Merge "Camera: Refine enforcement of supported resolutions in extensions" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 48d6392..8591a9c 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -77,6 +77,7 @@
"camera_platform_flags_core_java_lib",
"com.android.hardware.input-aconfig-java",
"com.android.input.flags-aconfig-java",
+ "com.android.internal.compat.flags-aconfig-java",
"com.android.internal.foldables.flags-aconfig-java",
"com.android.internal.pm.pkg.component.flags-aconfig-java",
"com.android.media.flags.bettertogether-aconfig-java",
@@ -663,6 +664,13 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// Platform Compat
+java_aconfig_library {
+ name: "com.android.internal.compat.flags-aconfig-java",
+ aconfig_declarations: "compat_logging_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Multi user
aconfig_declarations {
name: "android.multiuser.flags-aconfig",
diff --git a/apex/jobscheduler/service/Android.bp b/apex/jobscheduler/service/Android.bp
index 0104ee1..ace56d4 100644
--- a/apex/jobscheduler/service/Android.bp
+++ b/apex/jobscheduler/service/Android.bp
@@ -20,6 +20,7 @@
],
libs: [
+ "androidx.annotation_annotation",
"app-compat-annotations",
"error_prone_annotations",
"framework",
diff --git a/core/api/current.txt b/core/api/current.txt
index c810691..173cf6c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4482,7 +4482,7 @@
method @CallSuper public void onActionModeStarted(android.view.ActionMode);
method public void onActivityReenter(int, android.content.Intent);
method protected void onActivityResult(int, int, android.content.Intent);
- method @FlaggedApi("android.security.content_uri_permission_apis") public void onActivityResult(int, int, @NonNull android.content.Intent, @NonNull android.app.ComponentCaller);
+ method @FlaggedApi("android.security.content_uri_permission_apis") public void onActivityResult(int, int, @Nullable android.content.Intent, @NonNull android.app.ComponentCaller);
method @Deprecated public void onAttachFragment(android.app.Fragment);
method public void onAttachedToWindow();
method @Deprecated public void onBackPressed();
@@ -8084,7 +8084,7 @@
method public CharSequence getStartUserSessionMessage(@NonNull android.content.ComponentName);
method @Deprecated public boolean getStorageEncryption(@Nullable android.content.ComponentName);
method public int getStorageEncryptionStatus();
- method @FlaggedApi("android.app.admin.flags.esim_management_enabled") @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS) public java.util.Set<java.lang.Integer> getSubscriptionsIds();
+ method @FlaggedApi("android.app.admin.flags.esim_management_enabled") @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS) public java.util.Set<java.lang.Integer> getSubscriptionIds();
method @Nullable public android.app.admin.SystemUpdatePolicy getSystemUpdatePolicy();
method @Nullable public android.os.PersistableBundle getTransferOwnershipBundle();
method @Nullable public java.util.List<android.os.PersistableBundle> getTrustAgentConfiguration(@Nullable android.content.ComponentName, @NonNull android.content.ComponentName);
@@ -9602,8 +9602,8 @@
method public static android.appwidget.AppWidgetManager getInstance(android.content.Context);
method @FlaggedApi("android.appwidget.flags.generated_previews") @Nullable public android.widget.RemoteViews getWidgetPreview(@NonNull android.content.ComponentName, @Nullable android.os.UserHandle, int);
method public boolean isRequestPinAppWidgetSupported();
- method public void notifyAppWidgetViewDataChanged(int[], int);
- method public void notifyAppWidgetViewDataChanged(int, int);
+ method @Deprecated public void notifyAppWidgetViewDataChanged(int[], int);
+ method @Deprecated public void notifyAppWidgetViewDataChanged(int, int);
method public void partiallyUpdateAppWidget(int[], android.widget.RemoteViews);
method public void partiallyUpdateAppWidget(int, android.widget.RemoteViews);
method @FlaggedApi("android.appwidget.flags.generated_previews") public void removeWidgetPreview(@NonNull android.content.ComponentName, int);
@@ -19286,11 +19286,11 @@
method @NonNull public java.util.List<java.lang.Integer> getSupportedExtensions();
method public boolean isCaptureProcessProgressAvailable(int);
method public boolean isPostviewAvailable(int);
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Float>> EFV_PADDING_ZOOM_FACTOR_RANGE;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Float>> EFV_PADDING_ZOOM_FACTOR_RANGE;
field public static final int EXTENSION_AUTOMATIC = 0; // 0x0
field @Deprecated public static final int EXTENSION_BEAUTY = 1; // 0x1
field public static final int EXTENSION_BOKEH = 2; // 0x2
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final int EXTENSION_EYES_FREE_VIDEOGRAPHY = 5; // 0x5
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public static final int EXTENSION_EYES_FREE_VIDEOGRAPHY = 5; // 0x5
field public static final int EXTENSION_FACE_RETOUCH = 1; // 0x1
field public static final int EXTENSION_HDR = 3; // 0x3
field public static final int EXTENSION_NIGHT = 4; // 0x4
@@ -19890,30 +19890,30 @@
field public static final int MAX_THUMBNAIL_DIMENSION = 256; // 0x100
}
- @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class ExtensionCaptureRequest {
+ @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public final class ExtensionCaptureRequest {
ctor public ExtensionCaptureRequest();
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> EFV_AUTO_ZOOM;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_MAX_PADDING_ZOOM_FACTOR;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_PADDING_ZOOM_FACTOR;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_ROTATE_VIEWPORT;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> EFV_STABILIZATION_MODE;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final int EFV_STABILIZATION_MODE_GIMBAL = 1; // 0x1
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final int EFV_STABILIZATION_MODE_LOCKED = 2; // 0x2
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final int EFV_STABILIZATION_MODE_OFF = 0; // 0x0
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<android.util.Pair<java.lang.Integer,java.lang.Integer>> EFV_TRANSLATE_VIEWPORT;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> EFV_AUTO_ZOOM;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_MAX_PADDING_ZOOM_FACTOR;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_PADDING_ZOOM_FACTOR;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_ROTATE_VIEWPORT;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> EFV_STABILIZATION_MODE;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public static final int EFV_STABILIZATION_MODE_GIMBAL = 1; // 0x1
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public static final int EFV_STABILIZATION_MODE_LOCKED = 2; // 0x2
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public static final int EFV_STABILIZATION_MODE_OFF = 0; // 0x0
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<android.util.Pair<java.lang.Integer,java.lang.Integer>> EFV_TRANSLATE_VIEWPORT;
}
- @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class ExtensionCaptureResult {
+ @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public final class ExtensionCaptureResult {
ctor public ExtensionCaptureResult();
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> EFV_AUTO_ZOOM;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<int[]> EFV_AUTO_ZOOM_PADDING_REGION;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_MAX_PADDING_ZOOM_FACTOR;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<int[]> EFV_PADDING_REGION;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_PADDING_ZOOM_FACTOR;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_ROTATE_VIEWPORT;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EFV_STABILIZATION_MODE;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.graphics.PointF[]> EFV_TARGET_COORDINATES;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.util.Pair<java.lang.Integer,java.lang.Integer>> EFV_TRANSLATE_VIEWPORT;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> EFV_AUTO_ZOOM;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<int[]> EFV_AUTO_ZOOM_PADDING_REGION;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_MAX_PADDING_ZOOM_FACTOR;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<int[]> EFV_PADDING_REGION;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_PADDING_ZOOM_FACTOR;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_ROTATE_VIEWPORT;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EFV_STABILIZATION_MODE;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.graphics.PointF[]> EFV_TARGET_COORDINATES;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.util.Pair<java.lang.Integer,java.lang.Integer>> EFV_TRANSLATE_VIEWPORT;
}
public class MultiResolutionImageReader implements java.lang.AutoCloseable {
@@ -28054,7 +28054,7 @@
method public void sendSigningResult(@NonNull String, @NonNull byte[]);
method public void sendTrackInfoList(@Nullable java.util.List<android.media.tv.TvTrackInfo>);
method public void setCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.ad.TvAdView.TvAdCallback);
- method public void setOnUnhandledInputEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.ad.TvAdView.OnUnhandledInputEventListener);
+ method public void setOnUnhandledInputEventListener(@NonNull android.media.tv.ad.TvAdView.OnUnhandledInputEventListener);
method public boolean setTvView(@Nullable android.media.tv.TvView);
method public void startAdService();
method public void stopAdService();
@@ -52447,8 +52447,8 @@
method public final void cancelPendingInputEvents();
method public boolean checkInputConnectionProxy(android.view.View);
method public void clearAnimation();
- method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void clearCredentialManagerRequest();
method public void clearFocus();
+ method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void clearPendingCredentialRequest();
method public void clearViewTranslationCallback();
method public static int combineMeasuredStates(int, int);
method protected int computeHorizontalScrollExtent();
@@ -52557,8 +52557,6 @@
method @FlaggedApi("android.view.flags.sensitive_content_app_protection_api") public final int getContentSensitivity();
method @UiContext public final android.content.Context getContext();
method protected android.view.ContextMenu.ContextMenuInfo getContextMenuInfo();
- method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public final android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException> getCredentialManagerCallback();
- method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public final android.credentials.GetCredentialRequest getCredentialManagerRequest();
method public final boolean getDefaultFocusHighlightEnabled();
method public static int getDefaultSize(int, int);
method public android.view.Display getDisplay();
@@ -52643,6 +52641,8 @@
method public int getPaddingTop();
method public final android.view.ViewParent getParent();
method public android.view.ViewParent getParentForAccessibility();
+ method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public final android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException> getPendingCredentialCallback();
+ method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public final android.credentials.GetCredentialRequest getPendingCredentialRequest();
method public float getPivotX();
method public float getPivotY();
method public android.view.PointerIcon getPointerIcon();
@@ -52943,7 +52943,6 @@
method public void setContentDescription(CharSequence);
method @FlaggedApi("android.view.flags.sensitive_content_app_protection_api") public final void setContentSensitivity(int);
method public void setContextClickable(boolean);
- method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void setCredentialManagerRequest(@NonNull android.credentials.GetCredentialRequest, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
method public void setDefaultFocusHighlightEnabled(boolean);
method @Deprecated public void setDrawingCacheBackgroundColor(@ColorInt int);
method @Deprecated public void setDrawingCacheEnabled(boolean);
@@ -53022,6 +53021,7 @@
method public void setOverScrollMode(int);
method public void setPadding(int, int, int, int);
method public void setPaddingRelative(int, int, int, int);
+ method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void setPendingCredentialRequest(@NonNull android.credentials.GetCredentialRequest, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
method public void setPivotX(float);
method public void setPivotY(float);
method public void setPointerIcon(android.view.PointerIcon);
@@ -53825,10 +53825,10 @@
method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void clearCredentialManagerRequest();
method @Nullable public abstract android.view.autofill.AutofillId getAutofillId();
method public abstract int getChildCount();
- method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException> getCredentialManagerCallback();
- method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public android.credentials.GetCredentialRequest getCredentialManagerRequest();
method public abstract android.os.Bundle getExtras();
method public abstract CharSequence getHint();
+ method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException> getPendingCredentialCallback();
+ method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public android.credentials.GetCredentialRequest getPendingCredentialRequest();
method public abstract CharSequence getText();
method public abstract int getTextSelectionEnd();
method public abstract int getTextSelectionStart();
@@ -53851,7 +53851,6 @@
method public abstract void setClickable(boolean);
method public abstract void setContentDescription(CharSequence);
method public abstract void setContextClickable(boolean);
- method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void setCredentialManagerRequest(@NonNull android.credentials.GetCredentialRequest, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
method public abstract void setDataIsSensitive(boolean);
method public abstract void setDimens(int, int, int, int, int, int);
method public abstract void setElevation(float);
@@ -53870,6 +53869,7 @@
method public void setMaxTextLength(int);
method public void setMinTextEms(int);
method public abstract void setOpaque(boolean);
+ method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void setPendingCredentialRequest(@NonNull android.credentials.GetCredentialRequest, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
method public void setReceiveContentMimeTypes(@Nullable String[]);
method public abstract void setSelected(boolean);
method public abstract void setText(CharSequence);
@@ -57915,7 +57915,7 @@
method public abstract boolean getBuiltInZoomControls();
method public abstract int getCacheMode();
method public abstract String getCursiveFontFamily();
- method public abstract boolean getDatabaseEnabled();
+ method @Deprecated public abstract boolean getDatabaseEnabled();
method @Deprecated public abstract String getDatabasePath();
method public abstract int getDefaultFixedFontSize();
method public abstract int getDefaultFontSize();
@@ -57961,7 +57961,7 @@
method public abstract void setBuiltInZoomControls(boolean);
method public abstract void setCacheMode(int);
method public abstract void setCursiveFontFamily(String);
- method public abstract void setDatabaseEnabled(boolean);
+ method @Deprecated public abstract void setDatabaseEnabled(boolean);
method @Deprecated public abstract void setDatabasePath(String);
method public abstract void setDefaultFixedFontSize(int);
method public abstract void setDefaultFontSize(int);
@@ -60157,7 +60157,7 @@
method public void setRadioGroupChecked(@IdRes int, @IdRes int);
method public void setRelativeScrollPosition(@IdRes int, int);
method @Deprecated public void setRemoteAdapter(int, @IdRes int, android.content.Intent);
- method public void setRemoteAdapter(@IdRes int, android.content.Intent);
+ method @Deprecated public void setRemoteAdapter(@IdRes int, android.content.Intent);
method public void setRemoteAdapter(@IdRes int, @NonNull android.widget.RemoteViews.RemoteCollectionItems);
method public void setScrollPosition(@IdRes int, int);
method public void setShort(@IdRes int, String, short);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 0bc4b77..a551f0d 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1306,6 +1306,7 @@
public class DevicePolicyManager {
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public int checkProvisioningPrecondition(@NonNull String, @NonNull String);
+ method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void clearAuditLogEventCallback();
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException;
method @Nullable public android.content.Intent createProvisioningIntentFromNfcIntent(@NonNull android.content.Intent);
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void finalizeWorkProfileProvisioning(@NonNull android.os.UserHandle, @Nullable android.accounts.Account);
@@ -1342,7 +1343,7 @@
method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException;
method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public void setApplicationExemptions(@NonNull String, @NonNull java.util.Set<java.lang.Integer>) throws android.content.pm.PackageManager.NameNotFoundException;
method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEnabled(boolean);
- method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEventCallback(@NonNull java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.util.List<android.app.admin.SecurityLog.SecurityEvent>>);
+ method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEventCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.app.admin.SecurityLog.SecurityEvent>>);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied();
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setDpcDownloaded(boolean);
method @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setMaxPolicyStorageLimit(int);
@@ -2189,14 +2190,6 @@
package android.app.ondeviceintelligence {
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class Content implements android.os.Parcelable {
- ctor public Content(@NonNull android.os.Bundle);
- method public int describeContents();
- method @NonNull public android.os.Bundle getData();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.Content> CREATOR;
- }
-
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface DownloadCallback {
method public void onDownloadCompleted(@NonNull android.os.PersistableBundle);
method public void onDownloadFailed(int, @Nullable String, @NonNull android.os.PersistableBundle);
@@ -2232,11 +2225,11 @@
}
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class FeatureDetails implements android.os.Parcelable {
- ctor public FeatureDetails(@android.app.ondeviceintelligence.FeatureDetails.Status int, @NonNull android.os.PersistableBundle);
- ctor public FeatureDetails(@android.app.ondeviceintelligence.FeatureDetails.Status int);
+ ctor public FeatureDetails(int, @NonNull android.os.PersistableBundle);
+ ctor public FeatureDetails(int);
method public int describeContents();
method @NonNull public android.os.PersistableBundle getFeatureDetailParams();
- method @android.app.ondeviceintelligence.FeatureDetails.Status public int getFeatureStatus();
+ method public int getFeatureStatus();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.FeatureDetails> CREATOR;
field public static final int FEATURE_STATUS_AVAILABLE = 3; // 0x3
@@ -2246,35 +2239,14 @@
field public static final int FEATURE_STATUS_UNAVAILABLE = 0; // 0x0
}
- @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE_USE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD}) public static @interface FeatureDetails.Status {
- }
-
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public class OnDeviceIntelligenceManager {
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeature(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- method @Nullable @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public String getRemoteServicePackageName();
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getVersion(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.LongConsumer);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void listFeatures(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequest(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.ProcessingOutcomeReceiver);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestFeatureDownload(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.DownloadCallback);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestTokenInfo(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- field public static final int REQUEST_TYPE_EMBEDDINGS = 2; // 0x2
- field public static final int REQUEST_TYPE_INFERENCE = 0; // 0x0
- field public static final int REQUEST_TYPE_PREPARE = 1; // 0x1
- }
-
- public static class OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException extends java.lang.Exception {
- ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException(int, @NonNull String, @NonNull android.os.PersistableBundle);
- ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException(int, @NonNull android.os.PersistableBundle);
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public class OnDeviceIntelligenceException extends java.lang.Exception {
+ ctor public OnDeviceIntelligenceException(int, @NonNull String, @NonNull android.os.PersistableBundle);
+ ctor public OnDeviceIntelligenceException(int, @NonNull android.os.PersistableBundle);
+ ctor public OnDeviceIntelligenceException(int, @NonNull String);
+ ctor public OnDeviceIntelligenceException(int);
method public int getErrorCode();
method @NonNull public android.os.PersistableBundle getErrorParams();
- field public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 1000; // 0x3e8
- }
-
- public static class OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException extends android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException {
- ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException(int, @NonNull String, @NonNull android.os.PersistableBundle);
- ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException(int, @NonNull android.os.PersistableBundle);
+ field public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 100; // 0x64
field public static final int PROCESSING_ERROR_BAD_DATA = 2; // 0x2
field public static final int PROCESSING_ERROR_BAD_REQUEST = 3; // 0x3
field public static final int PROCESSING_ERROR_BUSY = 9; // 0x9
@@ -2290,10 +2262,28 @@
field public static final int PROCESSING_ERROR_SERVICE_UNAVAILABLE = 15; // 0xf
field public static final int PROCESSING_ERROR_SUSPENDED = 13; // 0xd
field public static final int PROCESSING_ERROR_UNKNOWN = 1; // 0x1
+ field public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 200; // 0xc8
}
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface ProcessingOutcomeReceiver extends android.os.OutcomeReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> {
- method public default void onDataAugmentRequest(@NonNull android.app.ondeviceintelligence.Content, @NonNull java.util.function.Consumer<android.app.ondeviceintelligence.Content>);
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class OnDeviceIntelligenceManager {
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeature(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+ method @Nullable @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public String getRemoteServicePackageName();
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getVersion(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.LongConsumer);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void listFeatures(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequest(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.ProcessingCallback);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamingProcessingCallback);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestFeatureDownload(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.DownloadCallback);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestTokenInfo(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+ field public static final int REQUEST_TYPE_EMBEDDINGS = 2; // 0x2
+ field public static final int REQUEST_TYPE_INFERENCE = 0; // 0x0
+ field public static final int REQUEST_TYPE_PREPARE = 1; // 0x1
+ }
+
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface ProcessingCallback {
+ method public default void onDataAugmentRequest(@NonNull android.os.Bundle, @NonNull java.util.function.Consumer<android.os.Bundle>);
+ method public void onError(@NonNull android.app.ondeviceintelligence.OnDeviceIntelligenceException);
+ method public void onResult(@NonNull android.os.Bundle);
}
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class ProcessingSignal {
@@ -2306,8 +2296,8 @@
method public void onSignalReceived(@NonNull android.os.PersistableBundle);
}
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamedProcessingOutcomeReceiver extends android.app.ondeviceintelligence.ProcessingOutcomeReceiver {
- method public void onNewContent(@NonNull android.app.ondeviceintelligence.Content);
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamingProcessingCallback extends android.app.ondeviceintelligence.ProcessingCallback {
+ method public void onPartialResult(@NonNull android.os.Bundle);
}
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class TokenInfo implements android.os.Parcelable {
@@ -3347,7 +3337,7 @@
field public static final int STATUS_SERVICE_UNAVAILABLE = 3; // 0x3
field public static final int STATUS_SUCCESS = 1; // 0x1
field public static final int STATUS_UNKNOWN = 0; // 0x0
- field public static final int STATUS_UNSUPPORTED = 2; // 0x2
+ field @Deprecated public static final int STATUS_UNSUPPORTED = 2; // 0x2
field @FlaggedApi("android.app.wearable.enable_data_request_observer_api") public static final int STATUS_UNSUPPORTED_DATA_TYPE = 8; // 0x8
field @FlaggedApi("android.app.wearable.enable_unsupported_operation_status_code") public static final int STATUS_UNSUPPORTED_OPERATION = 6; // 0x6
field public static final int STATUS_WEARABLE_UNAVAILABLE = 4; // 0x4
@@ -6260,7 +6250,7 @@
method public void addOnCompleteListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.radio.ProgramList.OnCompleteListener);
method public void addOnCompleteListener(@NonNull android.hardware.radio.ProgramList.OnCompleteListener);
method public void close();
- method @Nullable public android.hardware.radio.RadioManager.ProgramInfo get(@NonNull android.hardware.radio.ProgramSelector.Identifier);
+ method @Deprecated @Nullable public android.hardware.radio.RadioManager.ProgramInfo get(@NonNull android.hardware.radio.ProgramSelector.Identifier);
method @FlaggedApi("android.hardware.radio.hd_radio_improved") @NonNull public java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramInfos(@NonNull android.hardware.radio.ProgramSelector.Identifier);
method public void registerListCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.radio.ProgramList.ListCallback);
method public void registerListCallback(@NonNull android.hardware.radio.ProgramList.ListCallback);
@@ -6312,7 +6302,7 @@
field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5; // 0x5
field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SID_EXT = 5; // 0x5
field public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10; // 0xa
- field public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb
+ field @Deprecated public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb
field public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9; // 0x9
field public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3; // 0x3
field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int IDENTIFIER_TYPE_HD_STATION_LOCATION = 15; // 0xf
@@ -6320,8 +6310,8 @@
field @Deprecated public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4; // 0x4
field public static final int IDENTIFIER_TYPE_INVALID = 0; // 0x0
field public static final int IDENTIFIER_TYPE_RDS_PI = 2; // 0x2
- field public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd
- field public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc
+ field @Deprecated public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd
+ field @Deprecated public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc
field public static final int IDENTIFIER_TYPE_VENDOR_END = 1999; // 0x7cf
field @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = 1999; // 0x7cf
field @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = 1000; // 0x3e8
@@ -6374,7 +6364,7 @@
field public static final int CONFIG_DAB_DAB_SOFT_LINKING = 8; // 0x8
field public static final int CONFIG_DAB_FM_LINKING = 7; // 0x7
field public static final int CONFIG_DAB_FM_SOFT_LINKING = 9; // 0x9
- field public static final int CONFIG_FORCE_ANALOG = 2; // 0x2
+ field @Deprecated public static final int CONFIG_FORCE_ANALOG = 2; // 0x2
field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int CONFIG_FORCE_ANALOG_AM = 11; // 0xb
field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int CONFIG_FORCE_ANALOG_FM = 10; // 0xa
field public static final int CONFIG_FORCE_DIGITAL = 3; // 0x3
@@ -12955,38 +12945,26 @@
ctor public OnDeviceIntelligenceService();
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
method public abstract void onDownloadFeature(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull android.app.ondeviceintelligence.DownloadCallback);
- method public abstract void onGetFeature(int, int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- method public abstract void onGetFeatureDetails(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ method public abstract void onGetFeature(int, int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+ method public abstract void onGetFeatureDetails(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
method public abstract void onGetReadOnlyFeatureFileDescriptorMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,android.os.ParcelFileDescriptor>>);
method public abstract void onGetVersion(@NonNull java.util.function.LongConsumer);
method public abstract void onInferenceServiceConnected();
method public abstract void onInferenceServiceDisconnected();
- method public abstract void onListFeatures(int, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- method public final void updateProcessingState(@NonNull android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException>);
+ method public abstract void onListFeatures(int, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+ method public final void updateProcessingState(@NonNull android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceIntelligenceService";
}
- public abstract static class OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException extends java.lang.Exception {
- ctor public OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException(int);
- ctor public OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException(int, @NonNull String);
- method public int getErrorCode();
- }
-
- public static class OnDeviceIntelligenceService.OnDeviceUpdateProcessingException extends android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException {
- ctor public OnDeviceIntelligenceService.OnDeviceUpdateProcessingException(int);
- ctor public OnDeviceIntelligenceService.OnDeviceUpdateProcessingException(int, @NonNull String);
- field public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 1; // 0x1
- }
-
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceSandboxedInferenceService extends android.app.Service {
ctor public OnDeviceSandboxedInferenceService();
method public final void fetchFeatureFileInputStreamMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.io.FileInputStream>>);
method @NonNull public java.util.concurrent.Executor getCallbackExecutor();
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
- method @NonNull public abstract void onProcessRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.ProcessingOutcomeReceiver);
- method @NonNull public abstract void onProcessRequestStreaming(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver);
- method @NonNull public abstract void onTokenInfoRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
- method public abstract void onUpdateProcessingState(@NonNull android.os.Bundle, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException>);
+ method @NonNull public abstract void onProcessRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.ProcessingCallback);
+ method @NonNull public abstract void onProcessRequestStreaming(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamingProcessingCallback);
+ method @NonNull public abstract void onTokenInfoRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+ method public abstract void onUpdateProcessingState(@NonNull android.os.Bundle, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
method public final java.io.FileInputStream openFileInput(@NonNull String) throws java.io.FileNotFoundException;
method public final void openFileInputAsync(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.io.FileInputStream>) throws java.io.FileNotFoundException;
field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService";
@@ -14213,7 +14191,6 @@
method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsSupportingScheme(String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean isInEmergencyCall();
method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isInSelfManagedCall(@NonNull String, @NonNull android.os.UserHandle);
- method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isInSelfManagedCall(@NonNull String, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRinging();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUserSelectedOutgoingPhoneAccount(@Nullable android.telecom.PhoneAccountHandle);
field public static final String ACTION_CURRENT_TTY_MODE_CHANGED = "android.telecom.action.CURRENT_TTY_MODE_CHANGED";
@@ -15393,7 +15370,7 @@
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean matchesCurrentSimOperator(@NonNull String, int, @Nullable String);
method public boolean needsOtaServiceProvisioning();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled();
- method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(android.Manifest.permission.DUMP) public void persistEmergencyCallDiagnosticData(@NonNull String, @NonNull android.telephony.TelephonyManager.EmergencyCallDiagnosticData);
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(android.Manifest.permission.READ_DROPBOX_DATA) public void persistEmergencyCallDiagnosticData(@NonNull String, @NonNull android.telephony.TelephonyManager.EmergencyCallDiagnosticData);
method @RequiresPermission(android.Manifest.permission.REBOOT) public int prepareForUnattendedReboot();
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerCarrierPrivilegesCallback(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CarrierPrivilegesCallback);
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 1e72a06..62fc67b 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -1885,8 +1885,6 @@
Documentation mentions 'TODO'
Todo: android.app.NotificationManager#isNotificationAssistantAccessGranted(android.content.ComponentName):
Documentation mentions 'TODO'
-Todo: android.app.ondeviceintelligence.OnDeviceIntelligenceManager#requestFeatureDownload(android.app.ondeviceintelligence.Feature, android.app.ondeviceintelligence.CancellationSignal, java.util.concurrent.Executor, android.app.ondeviceintelligence.DownloadCallback):
- Documentation mentions 'TODO'
Todo: android.hardware.camera2.params.StreamConfigurationMap:
Documentation mentions 'TODO'
Todo: android.hardware.location.ContextHubManager#getNanoAppInstanceInfo(int):
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 892567c6..bc45a76 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -42,6 +42,7 @@
field public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE";
field public static final String READ_WRITE_SYNC_DISABLED_MODE_CONFIG = "android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG";
field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO";
+ field @FlaggedApi("android.permission.flags.sensitive_notification_app_protection") public static final String RECORD_SENSITIVE_CONTENT = "android.permission.RECORD_SENSITIVE_CONTENT";
field public static final String REMAP_MODIFIER_KEYS = "android.permission.REMAP_MODIFIER_KEYS";
field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
field public static final String REQUEST_UNIQUE_ID_ATTESTATION = "android.permission.REQUEST_UNIQUE_ID_ATTESTATION";
diff --git a/core/java/Android.bp b/core/java/Android.bp
index ab1c9a4..4f96206 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -167,6 +167,9 @@
"com/android/internal/logging/UiEventLoggerImpl.java",
":statslog-framework-java-gen",
],
+ libs: [
+ "androidx.annotation_annotation",
+ ],
static_libs: ["modules-utils-uieventlogger-interface"],
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index afbefca..1cc2d25 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -7473,7 +7473,7 @@
* intent.
*/
@FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS)
- public void onActivityResult(int requestCode, int resultCode, @NonNull Intent data,
+ public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data,
@NonNull ComponentCaller caller) {
onActivityResult(requestCode, resultCode, data);
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 1d39186..ae5cacd 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -974,6 +974,7 @@
ContentCaptureOptions contentCaptureOptions;
long[] disabledCompatChanges;
+ long[] mLoggableCompatChanges;
SharedMemory mSerializedSystemFontMap;
@@ -1283,6 +1284,7 @@
AutofillOptions autofillOptions,
ContentCaptureOptions contentCaptureOptions,
long[] disabledCompatChanges,
+ long[] loggableCompatChanges,
SharedMemory serializedSystemFontMap,
long startRequestedElapsedTime,
long startRequestedUptime) {
@@ -1337,6 +1339,7 @@
data.autofillOptions = autofillOptions;
data.contentCaptureOptions = contentCaptureOptions;
data.disabledCompatChanges = disabledCompatChanges;
+ data.mLoggableCompatChanges = loggableCompatChanges;
data.mSerializedSystemFontMap = serializedSystemFontMap;
data.startRequestedElapsedTime = startRequestedElapsedTime;
data.startRequestedUptime = startRequestedUptime;
@@ -4073,6 +4076,13 @@
ActivityManager.getService().waitForNetworkStateUpdate(mNetworkBlockSeq);
mNetworkBlockSeq = INVALID_PROC_STATE_SEQ;
} catch (RemoteException ignored) {}
+ if (Flags.clearDnsCacheOnNetworkRulesUpdate()) {
+ // InetAddress will cache UnknownHostException failures. If the rules got
+ // updated and the app has network access now, we need to clear the negative
+ // cache to ensure valid dns queries can work immediately.
+ // TODO: b/329133769 - Clear only the negative cache once it is available.
+ InetAddress.clearDnsCache();
+ }
}
}
}
@@ -7123,7 +7133,7 @@
Process.setStartTimes(SystemClock.elapsedRealtime(), SystemClock.uptimeMillis(),
data.startRequestedElapsedTime, data.startRequestedUptime);
- AppCompatCallbacks.install(data.disabledCompatChanges);
+ AppCompatCallbacks.install(data.disabledCompatChanges, data.mLoggableCompatChanges);
// Let libcore handle any compat changes after installing the list of compat changes.
AppSpecializationHooks.handleCompatChangesBeforeBindingApplication();
diff --git a/core/java/android/app/AppCompatCallbacks.java b/core/java/android/app/AppCompatCallbacks.java
index 134cef5..f2debfc 100644
--- a/core/java/android/app/AppCompatCallbacks.java
+++ b/core/java/android/app/AppCompatCallbacks.java
@@ -30,41 +30,59 @@
*/
public final class AppCompatCallbacks implements Compatibility.BehaviorChangeDelegate {
private final long[] mDisabledChanges;
+ private final long[] mLoggableChanges;
private final ChangeReporter mChangeReporter;
/**
- * Install this class into the current process.
+ * Install this class into the current process using the disabled and loggable changes lists.
*
* @param disabledChanges Set of compatibility changes that are disabled for this process.
+ * @param loggableChanges Set of compatibility changes that we want to log.
*/
- public static void install(long[] disabledChanges) {
- Compatibility.setBehaviorChangeDelegate(new AppCompatCallbacks(disabledChanges));
+ public static void install(long[] disabledChanges, long[] loggableChanges) {
+ Compatibility.setBehaviorChangeDelegate(
+ new AppCompatCallbacks(disabledChanges, loggableChanges));
}
- private AppCompatCallbacks(long[] disabledChanges) {
+ private AppCompatCallbacks(long[] disabledChanges, long[] loggableChanges) {
mDisabledChanges = Arrays.copyOf(disabledChanges, disabledChanges.length);
+ mLoggableChanges = Arrays.copyOf(loggableChanges, loggableChanges.length);
Arrays.sort(mDisabledChanges);
- mChangeReporter = new ChangeReporter(
- ChangeReporter.SOURCE_APP_PROCESS);
+ Arrays.sort(mLoggableChanges);
+ mChangeReporter = new ChangeReporter(ChangeReporter.SOURCE_APP_PROCESS);
+ }
+
+ /**
+ * Helper to determine if a list contains a changeId.
+ *
+ * @param list to search through
+ * @param changeId for which to search in the list
+ * @return true if the given changeId is found in the provided array.
+ */
+ private boolean changeIdInChangeList(long[] list, long changeId) {
+ return Arrays.binarySearch(list, changeId) >= 0;
}
public void onChangeReported(long changeId) {
- reportChange(changeId, ChangeReporter.STATE_LOGGED);
+ boolean isLoggable = changeIdInChangeList(mLoggableChanges, changeId);
+ reportChange(changeId, ChangeReporter.STATE_LOGGED, isLoggable);
}
public boolean isChangeEnabled(long changeId) {
- if (Arrays.binarySearch(mDisabledChanges, changeId) < 0) {
- // Not present in the disabled array
- reportChange(changeId, ChangeReporter.STATE_ENABLED);
+ boolean isEnabled = !changeIdInChangeList(mDisabledChanges, changeId);
+ boolean isLoggable = changeIdInChangeList(mLoggableChanges, changeId);
+ if (isEnabled) {
+ // Not present in the disabled changeId array
+ reportChange(changeId, ChangeReporter.STATE_ENABLED, isLoggable);
return true;
}
- reportChange(changeId, ChangeReporter.STATE_DISABLED);
+ reportChange(changeId, ChangeReporter.STATE_DISABLED, isLoggable);
return false;
}
- private void reportChange(long changeId, int state) {
+ private void reportChange(long changeId, int state, boolean isLoggable) {
int uid = Process.myUid();
- mChangeReporter.reportChange(uid, changeId, state);
+ mChangeReporter.reportChange(uid, changeId, state, isLoggable);
}
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index af56cb4..ed0c933 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -272,10 +272,17 @@
@UnsupportedAppUsage
private Context mOuterContext;
+
+ private final Object mThemeLock = new Object();
+
@UnsupportedAppUsage
+ @GuardedBy("mThemeLock")
private int mThemeResource = 0;
+
@UnsupportedAppUsage
+ @GuardedBy("mThemeLock")
private Resources.Theme mTheme = null;
+
@UnsupportedAppUsage
private PackageManager mPackageManager;
private Context mReceiverRestrictedContext = null;
@@ -288,7 +295,6 @@
private ContentCaptureOptions mContentCaptureOptions = null;
- private final Object mSync = new Object();
/**
* Indicates this {@link Context} can not handle UI components properly and is not associated
* with a {@link Display} instance.
@@ -340,21 +346,18 @@
*/
private boolean mOwnsToken = false;
- @GuardedBy("mSync")
- private File mDatabasesDir;
- @GuardedBy("mSync")
- @UnsupportedAppUsage
- private File mPreferencesDir;
- @GuardedBy("mSync")
- private File mFilesDir;
- @GuardedBy("mSync")
- private File mCratesDir;
- @GuardedBy("mSync")
- private File mNoBackupFilesDir;
- @GuardedBy("mSync")
- private File mCacheDir;
- @GuardedBy("mSync")
- private File mCodeCacheDir;
+ private final Object mDirsLock = new Object();
+ private volatile File mDatabasesDir;
+ @UnsupportedAppUsage private volatile File mPreferencesDir;
+ private volatile File mFilesDir;
+ private volatile File mCratesDir;
+ private volatile File mNoBackupFilesDir;
+ private volatile File[] mExternalFilesDirs;
+ private volatile File[] mObbDirs;
+ private volatile File mCacheDir;
+ private volatile File mCodeCacheDir;
+ private volatile File[] mExternalCacheDirs;
+ private volatile File[] mExternalMediaDirs;
// The system service cache for the system services that are cached per-ContextImpl.
@UnsupportedAppUsage
@@ -458,7 +461,7 @@
@Override
public void setTheme(int resId) {
- synchronized (mSync) {
+ synchronized (mThemeLock) {
if (mThemeResource != resId) {
mThemeResource = resId;
initializeTheme();
@@ -468,14 +471,14 @@
@Override
public int getThemeResId() {
- synchronized (mSync) {
+ synchronized (mThemeLock) {
return mThemeResource;
}
}
@Override
public Resources.Theme getTheme() {
- synchronized (mSync) {
+ synchronized (mThemeLock) {
if (mTheme != null) {
return mTheme;
}
@@ -488,6 +491,7 @@
}
}
+ @GuardedBy("mThemeLock")
private void initializeTheme() {
if (mTheme == null) {
mTheme = mResources.newTheme();
@@ -597,12 +601,18 @@
if (sp == null) {
checkMode(mode);
if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {
- if (isCredentialProtectedStorage()
- && !getSystemService(UserManager.class)
- .isUserUnlockingOrUnlocked(UserHandle.myUserId())) {
- throw new IllegalStateException("SharedPreferences in credential encrypted "
- + "storage are not available until after user (id "
- + UserHandle.myUserId() + ") is unlocked");
+ if (isCredentialProtectedStorage()) {
+ final UserManager um = getSystemService(UserManager.class);
+ if (um == null) {
+ throw new IllegalStateException("SharedPreferences cannot be accessed "
+ + "if UserManager is not available. "
+ + "(e.g. from inside an isolated process)");
+ }
+ if (!um.isUserUnlockingOrUnlocked(UserHandle.myUserId())) {
+ throw new IllegalStateException("SharedPreferences in "
+ + "credential encrypted storage are not available until after "
+ + "user (id " + UserHandle.myUserId() + ") is unlocked");
+ }
}
}
sp = new SharedPreferencesImpl(file, mode);
@@ -731,12 +741,18 @@
@UnsupportedAppUsage
private File getPreferencesDir() {
- synchronized (mSync) {
- if (mPreferencesDir == null) {
- mPreferencesDir = new File(getDataDir(), "shared_prefs");
+ File localPreferencesDir = mPreferencesDir;
+ if (localPreferencesDir == null) {
+ synchronized (mDirsLock) {
+ localPreferencesDir = mPreferencesDir;
+ if (localPreferencesDir == null) {
+ localPreferencesDir = new File(getDataDir(), "shared_prefs");
+ ensurePrivateDirExists(localPreferencesDir);
+ mPreferencesDir = localPreferencesDir;
+ }
}
- return ensurePrivateDirExists(mPreferencesDir);
}
+ return localPreferencesDir;
}
@Override
@@ -778,16 +794,16 @@
/**
* Common-path handling of app data dir creation
*/
- private static File ensurePrivateDirExists(File file) {
- return ensurePrivateDirExists(file, 0771, -1, null);
+ private static void ensurePrivateDirExists(File file) {
+ ensurePrivateDirExists(file, 0771, -1, null);
}
- private static File ensurePrivateCacheDirExists(File file, String xattr) {
+ private static void ensurePrivateCacheDirExists(File file, String xattr) {
final int gid = UserHandle.getCacheAppGid(Process.myUid());
- return ensurePrivateDirExists(file, 02771, gid, xattr);
+ ensurePrivateDirExists(file, 02771, gid, xattr);
}
- private static File ensurePrivateDirExists(File file, int mode, int gid, String xattr) {
+ private static void ensurePrivateDirExists(File file, int mode, int gid, String xattr) {
if (!file.exists()) {
final String path = file.getAbsolutePath();
try {
@@ -815,17 +831,22 @@
}
}
}
- return file;
}
@Override
public File getFilesDir() {
- synchronized (mSync) {
- if (mFilesDir == null) {
- mFilesDir = new File(getDataDir(), "files");
+ File localFilesDir = mFilesDir;
+ if (localFilesDir == null) {
+ localFilesDir = mFilesDir;
+ synchronized (mDirsLock) {
+ if (localFilesDir == null) {
+ localFilesDir = new File(getDataDir(), "files");
+ ensurePrivateDirExists(localFilesDir);
+ mFilesDir = localFilesDir;
+ }
}
- return ensurePrivateDirExists(mFilesDir);
}
+ return localFilesDir;
}
@Override
@@ -835,25 +856,37 @@
final Path absoluteNormalizedCratePath = cratesRootPath.resolve(crateId)
.toAbsolutePath().normalize();
- synchronized (mSync) {
- if (mCratesDir == null) {
- mCratesDir = cratesRootPath.toFile();
+ File localCratesDir = mCratesDir;
+ if (localCratesDir == null) {
+ synchronized (mDirsLock) {
+ localCratesDir = mCratesDir;
+ if (localCratesDir == null) {
+ localCratesDir = cratesRootPath.toFile();
+ ensurePrivateDirExists(localCratesDir);
+ mCratesDir = localCratesDir;
+ }
}
- ensurePrivateDirExists(mCratesDir);
}
- File cratedDir = absoluteNormalizedCratePath.toFile();
- return ensurePrivateDirExists(cratedDir);
+ File crateDir = absoluteNormalizedCratePath.toFile();
+ ensurePrivateDirExists(crateDir);
+ return crateDir;
}
@Override
public File getNoBackupFilesDir() {
- synchronized (mSync) {
- if (mNoBackupFilesDir == null) {
- mNoBackupFilesDir = new File(getDataDir(), "no_backup");
+ File localNoBackupFilesDir = mNoBackupFilesDir;
+ if (localNoBackupFilesDir == null) {
+ synchronized (mDirsLock) {
+ localNoBackupFilesDir = mNoBackupFilesDir;
+ if (localNoBackupFilesDir == null) {
+ localNoBackupFilesDir = new File(getDataDir(), "no_backup");
+ ensurePrivateDirExists(localNoBackupFilesDir);
+ mNoBackupFilesDir = localNoBackupFilesDir;
+ }
}
- return ensurePrivateDirExists(mNoBackupFilesDir);
}
+ return localNoBackupFilesDir;
}
@Override
@@ -865,13 +898,24 @@
@Override
public File[] getExternalFilesDirs(String type) {
- synchronized (mSync) {
- File[] dirs = Environment.buildExternalStorageAppFilesDirs(getPackageName());
- if (type != null) {
- dirs = Environment.buildPaths(dirs, type);
+ File[] localExternalFilesDirs = mExternalFilesDirs;
+ if (localExternalFilesDirs == null) {
+ synchronized (mDirsLock) {
+ localExternalFilesDirs = mExternalFilesDirs;
+ if (localExternalFilesDirs == null) {
+ localExternalFilesDirs =
+ Environment.buildExternalStorageAppFilesDirs(getPackageName());
+ if (type != null) {
+ localExternalFilesDirs =
+ Environment.buildPaths(localExternalFilesDirs, type);
+ }
+ localExternalFilesDirs = ensureExternalDirsExistOrFilter(
+ localExternalFilesDirs, true /* tryCreateInProcess */);
+ mExternalFilesDirs = localExternalFilesDirs;
+ }
}
- return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
}
+ return localExternalFilesDirs;
}
@Override
@@ -883,30 +927,51 @@
@Override
public File[] getObbDirs() {
- synchronized (mSync) {
- File[] dirs = Environment.buildExternalStorageAppObbDirs(getPackageName());
- return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
+ File[] localObbDirs = mObbDirs;
+ if (mObbDirs == null) {
+ synchronized (mDirsLock) {
+ localObbDirs = mObbDirs;
+ if (localObbDirs == null) {
+ localObbDirs = Environment.buildExternalStorageAppObbDirs(getPackageName());
+ localObbDirs = ensureExternalDirsExistOrFilter(
+ localObbDirs, true /* tryCreateInProcess */);
+ mObbDirs = localObbDirs;
+ }
+ }
}
+ return localObbDirs;
}
@Override
public File getCacheDir() {
- synchronized (mSync) {
- if (mCacheDir == null) {
- mCacheDir = new File(getDataDir(), "cache");
+ File localCacheDir = mCacheDir;
+ if (localCacheDir == null) {
+ synchronized (mDirsLock) {
+ localCacheDir = mCacheDir;
+ if (localCacheDir == null) {
+ localCacheDir = new File(getDataDir(), "cache");
+ ensurePrivateCacheDirExists(localCacheDir, XATTR_INODE_CACHE);
+ mCacheDir = localCacheDir;
+ }
}
- return ensurePrivateCacheDirExists(mCacheDir, XATTR_INODE_CACHE);
}
+ return localCacheDir;
}
@Override
public File getCodeCacheDir() {
- synchronized (mSync) {
- if (mCodeCacheDir == null) {
- mCodeCacheDir = getCodeCacheDirBeforeBind(getDataDir());
+ File localCodeCacheDir = mCodeCacheDir;
+ if (localCodeCacheDir == null) {
+ synchronized (mDirsLock) {
+ localCodeCacheDir = mCodeCacheDir;
+ if (localCodeCacheDir == null) {
+ localCodeCacheDir = getCodeCacheDirBeforeBind(getDataDir());
+ ensurePrivateCacheDirExists(localCodeCacheDir, XATTR_INODE_CODE_CACHE);
+ mCodeCacheDir = localCodeCacheDir;
+ }
}
- return ensurePrivateCacheDirExists(mCodeCacheDir, XATTR_INODE_CODE_CACHE);
}
+ return localCodeCacheDir;
}
/**
@@ -927,21 +992,37 @@
@Override
public File[] getExternalCacheDirs() {
- synchronized (mSync) {
- File[] dirs = Environment.buildExternalStorageAppCacheDirs(getPackageName());
- // We don't try to create cache directories in-process, because they need special
- // setup for accurate quota tracking. This ensures the cache dirs are always
- // created through StorageManagerService.
- return ensureExternalDirsExistOrFilter(dirs, false /* tryCreateInProcess */);
+ File[] localExternalCacheDirs = mExternalCacheDirs;
+ if (localExternalCacheDirs == null) {
+ synchronized (mDirsLock) {
+ localExternalCacheDirs = mExternalCacheDirs;
+ if (localExternalCacheDirs == null) {
+ localExternalCacheDirs =
+ Environment.buildExternalStorageAppCacheDirs(getPackageName());
+ localExternalCacheDirs = ensureExternalDirsExistOrFilter(
+ localExternalCacheDirs, false /* tryCreateInProcess */);
+ mExternalCacheDirs = localExternalCacheDirs;
+ }
+ }
}
+ return localExternalCacheDirs;
}
@Override
public File[] getExternalMediaDirs() {
- synchronized (mSync) {
- File[] dirs = Environment.buildExternalStorageAppMediaDirs(getPackageName());
- return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
+ File[] localExternalMediaDirs = mExternalMediaDirs;
+ if (localExternalMediaDirs == null) {
+ synchronized (mDirsLock) {
+ localExternalMediaDirs = mExternalMediaDirs;
+ if (localExternalMediaDirs == null) {
+ localExternalMediaDirs = Environment.buildExternalStorageAppMediaDirs(getPackageName());
+ localExternalMediaDirs = ensureExternalDirsExistOrFilter(
+ localExternalMediaDirs, true /* tryCreateInProcess */);
+ mExternalMediaDirs = localExternalMediaDirs;
+ }
+ }
}
+ return localExternalMediaDirs;
}
/**
@@ -1040,16 +1121,22 @@
}
private File getDatabasesDir() {
- synchronized (mSync) {
- if (mDatabasesDir == null) {
- if ("android".equals(getPackageName())) {
- mDatabasesDir = new File("/data/system");
- } else {
- mDatabasesDir = new File(getDataDir(), "databases");
+ File localDatabasesDir = mDatabasesDir;
+ if (localDatabasesDir == null) {
+ synchronized (mDirsLock) {
+ localDatabasesDir = mDatabasesDir;
+ if (localDatabasesDir == null) {
+ if ("android".equals(getPackageName())) {
+ localDatabasesDir = new File("/data/system");
+ } else {
+ localDatabasesDir = new File(getDataDir(), "databases");
+ }
+ ensurePrivateDirExists(localDatabasesDir);
+ mDatabasesDir = localDatabasesDir;
}
}
- return ensurePrivateDirExists(mDatabasesDir);
}
+ return localDatabasesDir;
}
@Override
diff --git a/core/java/android/app/DreamManager.java b/core/java/android/app/DreamManager.java
index 7c8b0fd..ef6982e 100644
--- a/core/java/android/app/DreamManager.java
+++ b/core/java/android/app/DreamManager.java
@@ -185,6 +185,22 @@
}
/**
+ * Whether dreaming can start given user settings and the current dock/charge state.
+ *
+ * @hide
+ */
+ @UserHandleAware
+ @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE)
+ public boolean canStartDreaming(boolean isScreenOn) {
+ try {
+ return mService.canStartDreaming(isScreenOn);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return false;
+ }
+
+ /**
* Returns whether the device is Dreaming.
*
* <p> This is only used for testing the dream service APIs.
diff --git a/core/java/android/app/GrammaticalInflectionManager.java b/core/java/android/app/GrammaticalInflectionManager.java
index 4ce983f..3e7d665 100644
--- a/core/java/android/app/GrammaticalInflectionManager.java
+++ b/core/java/android/app/GrammaticalInflectionManager.java
@@ -125,7 +125,10 @@
/**
* Get the current grammatical gender of privileged application from the encrypted file.
*
- * @return the value of grammatical gender
+ * @return the value of system grammatical gender only if the calling app has the permission,
+ * otherwise throwing an exception.
+ *
+ * @throws SecurityException if the caller does not have the required permission.
*
* @see Configuration#getGrammaticalGender
*/
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index a04620c..251e4e8 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -90,7 +90,7 @@
in CompatibilityInfo compatInfo, in Map services,
in Bundle coreSettings, in String buildSerial, in AutofillOptions autofillOptions,
in ContentCaptureOptions contentCaptureOptions, in long[] disabledCompatChanges,
- in SharedMemory serializedSystemFontMap,
+ in long[] loggableCompatChanges, in SharedMemory serializedSystemFontMap,
long startRequestedElapsedTime, long startRequestedUptime);
void runIsolatedEntryPoint(in String entryPoint, in String[] entryPointArgs);
void scheduleExit();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 79cb09d..cd4c0bc 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -7531,6 +7531,9 @@
/**
* Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is
* attached to.
+ * <p>
+ * Note: Calling build() multiple times returns the same Notification instance,
+ * so reusing a builder to create multiple Notifications is discouraged.
*
* @return the fully constructed Notification.
*/
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index d01626e..fa4a400 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -34,6 +34,8 @@
import android.app.contentsuggestions.IContentSuggestionsManager;
import android.app.ecm.EnhancedConfirmationFrameworkInitializer;
import android.app.job.JobSchedulerFrameworkInitializer;
+import android.app.ondeviceintelligence.IOnDeviceIntelligenceManager;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
import android.app.people.PeopleManager;
import android.app.prediction.AppPredictionManager;
import android.app.role.RoleFrameworkInitializer;
@@ -1589,6 +1591,19 @@
return new WearableSensingManager(ctx.getOuterContext(), manager);
}});
+ registerService(Context.ON_DEVICE_INTELLIGENCE_SERVICE, OnDeviceIntelligenceManager.class,
+ new CachedServiceFetcher<OnDeviceIntelligenceManager>() {
+ @Override
+ public OnDeviceIntelligenceManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder iBinder = ServiceManager.getServiceOrThrow(
+ Context.ON_DEVICE_INTELLIGENCE_SERVICE);
+ IOnDeviceIntelligenceManager manager =
+ IOnDeviceIntelligenceManager.Stub.asInterface(iBinder);
+ return new OnDeviceIntelligenceManager(ctx.getOuterContext(), manager);
+ }
+ });
+
registerService(Context.GRAMMATICAL_INFLECTION_SERVICE, GrammaticalInflectionManager.class,
new CachedServiceFetcher<GrammaticalInflectionManager>() {
@Override
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 986205a..9ef8b38 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -189,10 +189,13 @@
@FlaggedApi(FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED)
public static final int HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER = 2;
+ /**
+ * @hide
+ */
@IntDef({HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED, HEADLESS_DEVICE_OWNER_MODE_AFFILIATED,
HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER})
@Retention(RetentionPolicy.SOURCE)
- private @interface HeadlessDeviceOwnerMode {}
+ public @interface HeadlessDeviceOwnerMode {}
/** @hide */
public static class PolicyInfo {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index b25ebf6..cb4ed058 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -53,6 +53,7 @@
import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY;
import static android.Manifest.permission.SET_TIME;
import static android.Manifest.permission.SET_TIME_ZONE;
+import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
import static android.app.admin.flags.Flags.FLAG_DEVICE_THEFT_API_ENABLED;
import static android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED;
import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED;
@@ -93,6 +94,7 @@
import android.app.IServiceConnection;
import android.app.KeyguardManager;
import android.app.admin.SecurityLog.SecurityEvent;
+import android.app.admin.flags.Flags;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
@@ -14090,9 +14092,7 @@
try {
return mService.isAuditLogEnabled(mContext.getPackageName());
} catch (RemoteException re) {
- re.rethrowFromSystemServer();
- // unreachable
- return false;
+ throw re.rethrowFromSystemServer();
}
}
@@ -14102,8 +14102,8 @@
* is enforced by the caller. Disabling the policy clears the callback. Each time a new callback
* is set, it will first be invoked with all the audit log events available at the time.
*
- * @param callback callback to invoke when new audit log events become available or {@code null}
- * to clear the callback.
+ * @param callback The callback to invoke when new audit log events become available.
+ * @param executor The executor through which the callback should be invoked.
* @hide
*/
@SystemApi
@@ -14111,11 +14111,10 @@
@RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
public void setAuditLogEventCallback(
@NonNull @CallbackExecutor Executor executor,
- @Nullable Consumer<List<SecurityEvent>> callback) {
+ @NonNull Consumer<List<SecurityEvent>> callback) {
throwIfParentInstance("setAuditLogEventCallback");
- final IAuditLogEventsCallback wrappedCallback = callback == null
- ? null
- : new IAuditLogEventsCallback.Stub() {
+ final IAuditLogEventsCallback wrappedCallback =
+ new IAuditLogEventsCallback.Stub() {
@Override
public void onNewAuditLogEvents(List<SecurityEvent> events) {
executor.execute(() -> callback.accept(events));
@@ -14124,7 +14123,25 @@
try {
mService.setAuditLogEventsCallback(mContext.getPackageName(), wrappedCallback);
} catch (RemoteException re) {
- re.rethrowFromSystemServer();
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Clears audit log event callback. If a callback was set previously, it may still get invoked
+ * after this call returns if it was already scheduled.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
+ @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
+ public void clearAuditLogEventCallback() {
+ throwIfParentInstance("clearAuditLogEventCallback");
+ try {
+ mService.setAuditLogEventsCallback(mContext.getPackageName(), null);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
}
}
@@ -17442,24 +17459,24 @@
}
/**
- * Returns the subscription ids of all subscriptions which was downloaded by the calling
+ * Returns the subscription ids of all subscriptions which were downloaded by the calling
* admin.
*
* <p> This returns only the subscriptions which were downloaded by the calling admin via
* {@link android.telephony.euicc.EuiccManager#downloadSubscription}.
- * If a susbcription is returned by this method then in it subject to management controls
+ * If a subscription is returned by this method then in it subject to management controls
* and cannot be removed by users.
*
* <p> Callable by device owners and profile owners.
*
- * @throws SecurityException if the caller is not authorized to call this method
- * @return ids of all managed subscriptions currently downloaded by an admin on the device
+ * @throws SecurityException if the caller is not authorized to call this method.
+ * @return ids of all managed subscriptions currently downloaded by an admin on the device.
*/
@FlaggedApi(FLAG_ESIM_MANAGEMENT_ENABLED)
@RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS)
@NonNull
- public Set<Integer> getSubscriptionsIds() {
- throwIfParentInstance("getSubscriptionsIds");
+ public Set<Integer> getSubscriptionIds() {
+ throwIfParentInstance("getSubscriptionIds");
if (mService != null) {
try {
return intArrayToSet(mService.getSubscriptionIds(mContext.getPackageName()));
@@ -17511,4 +17528,25 @@
}
return -1;
}
+
+ /**
+ * @return The headless device owner mode for the current set DO, returns
+ * {@link DeviceAdminInfo#HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED} if no DO is set.
+ *
+ * @hide
+ */
+ @DeviceAdminInfo.HeadlessDeviceOwnerMode
+ public int getHeadlessDeviceOwnerMode() {
+ if (!Flags.headlessDeviceOwnerProvisioningFixEnabled()) {
+ return HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
+ }
+ if (mService != null) {
+ try {
+ return mService.getHeadlessDeviceOwnerMode(mContext.getPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
+ }
}
\ No newline at end of file
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 3a7a891c..03d0b0f 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -625,4 +625,6 @@
void setMaxPolicyStorageLimit(String packageName, int storageLimit);
int getMaxPolicyStorageLimit(String packageName);
+
+ int getHeadlessDeviceOwnerMode(String callerPackageName);
}
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 1927019..c29ea6d 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -163,3 +163,13 @@
description: "Enable UX changes for esim management"
bug: "295301164"
}
+
+flag {
+ name: "headless_device_owner_provisioning_fix_enabled"
+ namespace: "enterprise"
+ description: "Fix provisioning for single-user headless DO"
+ bug: "289515470"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 89199ca..7548562 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -1297,7 +1297,7 @@
*/
@FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
@Nullable
- public GetCredentialRequest getCredentialManagerRequest() {
+ public GetCredentialRequest getPendingCredentialRequest() {
return mGetCredentialRequest;
}
@@ -1306,7 +1306,7 @@
*/
@FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
@Nullable
- public ResultReceiver getCredentialManagerCallback() {
+ public ResultReceiver getPendingCredentialCallback() {
return mGetCredentialResultReceiver;
}
@@ -2191,14 +2191,14 @@
@Nullable
@Override
- public GetCredentialRequest getCredentialManagerRequest() {
+ public GetCredentialRequest getPendingCredentialRequest() {
return mNode.mGetCredentialRequest;
}
@Nullable
@Override
public OutcomeReceiver<
- GetCredentialResponse, GetCredentialException> getCredentialManagerCallback() {
+ GetCredentialResponse, GetCredentialException> getPendingCredentialCallback() {
return mNode.mGetCredentialCallback;
}
@@ -2267,7 +2267,7 @@
}
@Override
- public void setCredentialManagerRequest(@NonNull GetCredentialRequest request,
+ public void setPendingCredentialRequest(@NonNull GetCredentialRequest request,
@NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
mNode.mGetCredentialRequest = request;
mNode.mGetCredentialCallback = callback;
@@ -2654,7 +2654,7 @@
+ ", isCredential=" + node.isCredential()
);
}
- GetCredentialRequest getCredentialRequest = node.getCredentialManagerRequest();
+ GetCredentialRequest getCredentialRequest = node.getPendingCredentialRequest();
if (getCredentialRequest == null) {
Log.i(TAG, prefix + " No Credential Manager Request");
} else {
diff --git a/core/java/android/app/network-policy.aconfig b/core/java/android/app/network-policy.aconfig
new file mode 100644
index 0000000..88f386f
--- /dev/null
+++ b/core/java/android/app/network-policy.aconfig
@@ -0,0 +1,11 @@
+package: "android.app"
+
+flag {
+ namespace: "backstage_power"
+ name: "clear_dns_cache_on_network_rules_update"
+ description: "Clears the DNS cache when the network rules update"
+ bug: "237556596"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
\ No newline at end of file
diff --git a/core/java/android/app/ondeviceintelligence/Content.java b/core/java/android/app/ondeviceintelligence/Content.java
deleted file mode 100644
index 51bd156..0000000
--- a/core/java/android/app/ondeviceintelligence/Content.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.ondeviceintelligence;
-
-import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
-
-import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
-import android.annotation.SystemApi;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.util.Objects;
-
-/**
- * Represents content sent to and received from the on-device inference service.
- * Can contain a collection of text, image, and binary parts or any combination of these.
- *
- * @hide
- */
-@SystemApi
-@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-public final class Content implements Parcelable {
- //TODO: Improve javadoc after adding validation logic.
- private static final String TAG = "Content";
- private final Bundle mData;
-
- /**
- * Create a content object using a Bundle of only known types that are read-only.
- */
- public Content(@NonNull Bundle data) {
- Objects.requireNonNull(data);
- validateBundleData(data);
- this.mData = data;
- }
-
- /**
- * Returns the Content's data represented as a Bundle.
- */
- @NonNull
- public Bundle getData() {
- return mData;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeBundle(mData);
- }
-
- @Override
- public int describeContents() {
- int mask = 0;
- mask |= mData.describeContents();
- return mask;
- }
-
- @NonNull
- public static final Creator<Content> CREATOR = new Creator<>() {
- @Override
- @NonNull
- public Content createFromParcel(@NonNull Parcel in) {
- return new Content(in.readBundle(getClass().getClassLoader()));
- }
-
- @Override
- @NonNull
- public Content[] newArray(int size) {
- return new Content[size];
- }
- };
-
- private void validateBundleData(Bundle unused) {
- // TODO: Validate there are only known types.
- }
-}
diff --git a/core/java/android/app/ondeviceintelligence/DownloadCallback.java b/core/java/android/app/ondeviceintelligence/DownloadCallback.java
index 684c71f..30c6e19 100644
--- a/core/java/android/app/ondeviceintelligence/DownloadCallback.java
+++ b/core/java/android/app/ondeviceintelligence/DownloadCallback.java
@@ -105,7 +105,7 @@
}
/**
- * Called when model download via MDD completed. The remote implementation can populate any
+ * Called when model download is completed. The remote implementation can populate any
* associated download params like file stats etc. in this callback to inform the client.
*
* @param downloadParams params containing info about the completed download.
diff --git a/core/java/android/app/ondeviceintelligence/Feature.java b/core/java/android/app/ondeviceintelligence/Feature.java
index 4a38c92..fd0379a 100644
--- a/core/java/android/app/ondeviceintelligence/Feature.java
+++ b/core/java/android/app/ondeviceintelligence/Feature.java
@@ -34,7 +34,6 @@
@SystemApi
@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
public final class Feature implements Parcelable {
- // TODO(b/325315604) - Check if we can expose non-hidden IntDefs in Framework.
private final int mId;
@Nullable
private final String mName;
diff --git a/core/java/android/app/ondeviceintelligence/FeatureDetails.java b/core/java/android/app/ondeviceintelligence/FeatureDetails.java
index f3cbd26..44930f2 100644
--- a/core/java/android/app/ondeviceintelligence/FeatureDetails.java
+++ b/core/java/android/app/ondeviceintelligence/FeatureDetails.java
@@ -60,6 +60,9 @@
/** Underlying service is unavailable and feature status cannot be fetched. */
public static final int FEATURE_STATUS_SERVICE_UNAVAILABLE = 4;
+ /**
+ * @hide
+ */
@IntDef(value = {
FEATURE_STATUS_UNAVAILABLE,
FEATURE_STATUS_DOWNLOADABLE,
diff --git a/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl b/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
index aba563f..8fc269e 100644
--- a/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
+++ b/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
@@ -16,15 +16,14 @@
package android.app.ondeviceintelligence;
-import android.app.ondeviceintelligence.IProcessingSignal;
import android.os.PersistableBundle;
/**
- * Interface for Download callback to passed onto service implementation,
+ * Interface for Download callback to be passed onto service implementation,
*
* @hide
*/
-oneway interface IDownloadCallback {
+interface IDownloadCallback {
void onDownloadStarted(long bytesToDownload) = 1;
void onDownloadProgress(long bytesDownloaded) = 2;
void onDownloadFailed(int failureStatus, String errorMessage, in PersistableBundle errorParams) = 3;
diff --git a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
index 360a809..0dbe181 100644
--- a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
+++ b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
@@ -21,7 +21,7 @@
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
- import android.app.ondeviceintelligence.Content;
+ import android.os.Bundle;
import android.app.ondeviceintelligence.Feature;
import android.app.ondeviceintelligence.FeatureDetails;
import android.app.ondeviceintelligence.IDownloadCallback;
@@ -39,7 +39,7 @@
*
* @hide
*/
- oneway interface IOnDeviceIntelligenceManager {
+ interface IOnDeviceIntelligenceManager {
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
void getVersion(in RemoteCallback remoteCallback) = 1;
@@ -53,18 +53,20 @@
void getFeatureDetails(in Feature feature, in IFeatureDetailsCallback featureDetailsCallback) = 4;
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
- void requestFeatureDownload(in Feature feature, ICancellationSignal signal, in IDownloadCallback callback) = 5;
+ void requestFeatureDownload(in Feature feature, in ICancellationSignal signal, in IDownloadCallback callback) = 5;
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
- void requestTokenInfo(in Feature feature, in Content request, in ICancellationSignal signal,
+ void requestTokenInfo(in Feature feature, in Bundle requestBundle, in ICancellationSignal signal,
in ITokenInfoCallback tokenInfocallback) = 6;
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
- void processRequest(in Feature feature, in Content request, int requestType, in ICancellationSignal cancellationSignal, in IProcessingSignal signal,
- in IResponseCallback responseCallback) = 7;
+ void processRequest(in Feature feature, in Bundle requestBundle, int requestType, in ICancellationSignal cancellationSignal,
+ in IProcessingSignal signal, in IResponseCallback responseCallback) = 7;
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
void processRequestStreaming(in Feature feature,
- in Content request, int requestType, in ICancellationSignal cancellationSignal, in IProcessingSignal signal,
+ in Bundle requestBundle, int requestType, in ICancellationSignal cancellationSignal, in IProcessingSignal signal,
in IStreamingResponseCallback streamingCallback) = 8;
+
+ String getRemoteServicePackageName() = 9;
}
diff --git a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl b/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
index 0adf305..45963d2 100644
--- a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
+++ b/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
@@ -1,8 +1,7 @@
package android.app.ondeviceintelligence;
-import android.app.ondeviceintelligence.Content;
-import android.app.ondeviceintelligence.IProcessingSignal;
import android.os.PersistableBundle;
+import android.os.Bundle;
import android.os.RemoteCallback;
/**
@@ -11,7 +10,7 @@
* @hide
*/
interface IResponseCallback {
- void onSuccess(in Content result) = 1;
+ void onSuccess(in Bundle resultBundle) = 1;
void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
- void onDataAugmentRequest(in Content content, in RemoteCallback contentCallback) = 3;
+ void onDataAugmentRequest(in Bundle processedContent, in RemoteCallback responseCallback) = 3;
}
diff --git a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl b/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
index 132e53e..671abe3 100644
--- a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
+++ b/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
@@ -1,10 +1,8 @@
package android.app.ondeviceintelligence;
-import android.app.ondeviceintelligence.Content;
-import android.app.ondeviceintelligence.IResponseCallback;
-import android.app.ondeviceintelligence.IProcessingSignal;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
+import android.os.Bundle;
/**
@@ -13,8 +11,8 @@
* @hide
*/
interface IStreamingResponseCallback {
- void onNewContent(in Content result) = 1;
- void onSuccess(in Content result) = 2;
+ void onNewContent(in Bundle processedResult) = 1;
+ void onSuccess(in Bundle result) = 2;
void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 3;
- void onDataAugmentRequest(in Content content, in RemoteCallback contentCallback) = 4;
+ void onDataAugmentRequest(in Bundle processedContent, in RemoteCallback responseCallback) = 4;
}
diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java
new file mode 100644
index 0000000..03ff563a
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.ondeviceintelligence;
+
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.PersistableBundle;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/**
+ * Exception type to be used for errors related to on-device intelligence system service with
+ * appropriate error code.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public class OnDeviceIntelligenceException extends Exception {
+
+ public static final int PROCESSING_ERROR_UNKNOWN = 1;
+
+ /** Request passed contains bad data for e.g. format. */
+ public static final int PROCESSING_ERROR_BAD_DATA = 2;
+
+ /** Bad request for inputs. */
+ public static final int PROCESSING_ERROR_BAD_REQUEST = 3;
+
+ /** Whole request was classified as not safe, and no response will be generated. */
+ public static final int PROCESSING_ERROR_REQUEST_NOT_SAFE = 4;
+
+ /** Underlying processing encountered an error and failed to compute results. */
+ public static final int PROCESSING_ERROR_COMPUTE_ERROR = 5;
+
+ /** Encountered an error while performing IPC */
+ public static final int PROCESSING_ERROR_IPC_ERROR = 6;
+
+ /** Request was cancelled either by user signal or by the underlying implementation. */
+ public static final int PROCESSING_ERROR_CANCELLED = 7;
+
+ /** Underlying processing in the remote implementation is not available. */
+ public static final int PROCESSING_ERROR_NOT_AVAILABLE = 8;
+
+ /** The service is currently busy. Callers should retry with exponential backoff. */
+ public static final int PROCESSING_ERROR_BUSY = 9;
+
+ /** Something went wrong with safety classification service. */
+ public static final int PROCESSING_ERROR_SAFETY_ERROR = 10;
+
+ /** Response generated was classified unsafe. */
+ public static final int PROCESSING_ERROR_RESPONSE_NOT_SAFE = 11;
+
+ /** Request is too large to be processed. */
+ public static final int PROCESSING_ERROR_REQUEST_TOO_LARGE = 12;
+
+ /** Inference suspended so that higher-priority inference can run. */
+ public static final int PROCESSING_ERROR_SUSPENDED = 13;
+
+ /**
+ * Underlying processing encountered an internal error, like a violated precondition
+ * .
+ */
+ public static final int PROCESSING_ERROR_INTERNAL = 14;
+
+ /**
+ * The processing was not able to be passed on to the remote implementation, as the
+ * service
+ * was unavailable.
+ */
+ public static final int PROCESSING_ERROR_SERVICE_UNAVAILABLE = 15;
+ /**
+ * Error code returned when the OnDeviceIntelligenceManager service is unavailable.
+ */
+ public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 100;
+
+ /**
+ * The connection to remote service failed and the processing state could not be updated.
+ */
+ public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 200;
+
+
+ /**
+ * Error code associated with the on-device intelligence failure.
+ *
+ * @hide
+ */
+ @IntDef(
+ value = {
+ PROCESSING_ERROR_UNKNOWN,
+ PROCESSING_ERROR_BAD_DATA,
+ PROCESSING_ERROR_BAD_REQUEST,
+ PROCESSING_ERROR_REQUEST_NOT_SAFE,
+ PROCESSING_ERROR_COMPUTE_ERROR,
+ PROCESSING_ERROR_IPC_ERROR,
+ PROCESSING_ERROR_CANCELLED,
+ PROCESSING_ERROR_NOT_AVAILABLE,
+ PROCESSING_ERROR_BUSY,
+ PROCESSING_ERROR_SAFETY_ERROR,
+ PROCESSING_ERROR_RESPONSE_NOT_SAFE,
+ PROCESSING_ERROR_REQUEST_TOO_LARGE,
+ PROCESSING_ERROR_SUSPENDED,
+ PROCESSING_ERROR_INTERNAL,
+ PROCESSING_ERROR_SERVICE_UNAVAILABLE,
+ ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+ PROCESSING_UPDATE_STATUS_CONNECTION_FAILED
+ }, open = true)
+ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+ @interface OnDeviceIntelligenceError {
+ }
+
+ private final int mErrorCode;
+ private final PersistableBundle mErrorParams;
+
+ /** Returns the error code of the exception. */
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+
+ /** Returns the error params of the exception. */
+ @NonNull
+ public PersistableBundle getErrorParams() {
+ return mErrorParams;
+ }
+
+ /**
+ * Creates a new OnDeviceIntelligenceException with the specified error code, error message and
+ * error params.
+ *
+ * @param errorCode The error code.
+ * @param errorMessage The error message.
+ * @param errorParams The error params.
+ */
+ public OnDeviceIntelligenceException(
+ @OnDeviceIntelligenceError int errorCode, @NonNull String errorMessage,
+ @NonNull PersistableBundle errorParams) {
+ super(errorMessage);
+ this.mErrorCode = errorCode;
+ this.mErrorParams = errorParams;
+ }
+
+ /**
+ * Creates a new OnDeviceIntelligenceException with the specified error code and error params.
+ *
+ * @param errorCode The error code.
+ * @param errorParams The error params.
+ */
+ public OnDeviceIntelligenceException(
+ @OnDeviceIntelligenceError int errorCode,
+ @NonNull PersistableBundle errorParams) {
+ this.mErrorCode = errorCode;
+ this.mErrorParams = errorParams;
+ }
+
+ /**
+ * Creates a new OnDeviceIntelligenceException with the specified error code and error message.
+ *
+ * @param errorCode The error code.
+ * @param errorMessage The error message.
+ */
+ public OnDeviceIntelligenceException(
+ @OnDeviceIntelligenceError int errorCode, @NonNull String errorMessage) {
+ super(errorMessage);
+ this.mErrorCode = errorCode;
+ this.mErrorParams = new PersistableBundle();
+ }
+
+ /**
+ * Creates a new OnDeviceIntelligenceException with the specified error code.
+ *
+ * @param errorCode The error code.
+ */
+ public OnDeviceIntelligenceException(
+ @OnDeviceIntelligenceError int errorCode) {
+ this.mErrorCode = errorCode;
+ this.mErrorParams = new PersistableBundle();
+ }
+}
diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
index d195c4d..a465e3c 100644
--- a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
+++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
@@ -28,6 +28,7 @@
import android.annotation.SystemService;
import android.content.ComponentName;
import android.content.Context;
+import android.graphics.Bitmap;
import android.os.Binder;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -36,6 +37,7 @@
import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.system.OsConstants;
import androidx.annotation.IntDef;
@@ -63,7 +65,7 @@
@SystemApi
@SystemService(Context.ON_DEVICE_INTELLIGENCE_SERVICE)
@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-public class OnDeviceIntelligenceManager {
+public final class OnDeviceIntelligenceManager {
/**
* @hide
*/
@@ -118,14 +120,13 @@
@Nullable
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
public String getRemoteServicePackageName() {
- String serviceConfigValue = mContext.getResources().getString(
- R.string.config_defaultOnDeviceSandboxedInferenceService);
- ComponentName componentName = ComponentName.unflattenFromString(serviceConfigValue);
- if (componentName != null) {
- return componentName.getPackageName();
+ String result;
+ try{
+ result = mService.getRemoteServicePackageName();
+ } catch (RemoteException e){
+ throw e.rethrowFromSystemServer();
}
-
- return null;
+ return result;
}
/**
@@ -139,7 +140,7 @@
public void getFeature(
int featureId,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull OutcomeReceiver<Feature, OnDeviceIntelligenceManagerException> featureReceiver) {
+ @NonNull OutcomeReceiver<Feature, OnDeviceIntelligenceException> featureReceiver) {
try {
IFeatureCallback callback =
new IFeatureCallback.Stub() {
@@ -154,7 +155,7 @@
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> featureReceiver.onError(
- new OnDeviceIntelligenceManagerException(
+ new OnDeviceIntelligenceException(
errorCode, errorMessage, errorParams))));
}
};
@@ -173,7 +174,7 @@
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
public void listFeatures(
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull OutcomeReceiver<List<Feature>, OnDeviceIntelligenceManagerException> featureListReceiver) {
+ @NonNull OutcomeReceiver<List<Feature>, OnDeviceIntelligenceException> featureListReceiver) {
try {
IListFeaturesCallback callback =
new IListFeaturesCallback.Stub() {
@@ -188,7 +189,7 @@
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> featureListReceiver.onError(
- new OnDeviceIntelligenceManagerException(
+ new OnDeviceIntelligenceException(
errorCode, errorMessage, errorParams))));
}
};
@@ -211,7 +212,7 @@
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
public void getFeatureDetails(@NonNull Feature feature,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull OutcomeReceiver<FeatureDetails, OnDeviceIntelligenceManagerException> featureDetailsReceiver) {
+ @NonNull OutcomeReceiver<FeatureDetails, OnDeviceIntelligenceException> featureDetailsReceiver) {
try {
IFeatureDetailsCallback callback = new IFeatureDetailsCallback.Stub() {
@@ -226,7 +227,7 @@
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> featureDetailsReceiver.onError(
- new OnDeviceIntelligenceManagerException(errorCode,
+ new OnDeviceIntelligenceException(errorCode,
errorMessage, errorParams))));
}
};
@@ -243,9 +244,8 @@
*
* Note: If a feature was already requested for downloaded previously, the onDownloadFailed
* callback would be invoked with {@link DownloadCallback#DOWNLOAD_FAILURE_STATUS_DOWNLOADING}.
- * In such cases, clients should query the feature status via {@link #getFeatureStatus} to
- * check
- * on the feature's download status.
+ * In such cases, clients should query the feature status via {@link #getFeatureDetails} to
+ * check on the feature's download status.
*
* @param feature feature to request download for.
* @param callback callback to populate updates about download status.
@@ -284,7 +284,7 @@
@Override
public void onDownloadCompleted(PersistableBundle downloadParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
- () -> onDownloadCompleted(downloadParams)));
+ () -> callback.onDownloadCompleted(downloadParams)));
}
};
@@ -305,7 +305,8 @@
* provided {@link Feature}.
*
* @param feature feature associated with the request.
- * @param request request that contains the content data and associated params.
+ * @param request request and associated params represented by the Bundle
+ * data.
* @param outcomeReceiver callback to populate the token info or exception in case of
* failure.
* @param cancellationSignal signal to invoke cancellation on the operation in the remote
@@ -313,11 +314,11 @@
* @param callbackExecutor executor to run the callback on.
*/
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
- public void requestTokenInfo(@NonNull Feature feature, @NonNull Content request,
+ public void requestTokenInfo(@NonNull Feature feature, @NonNull @InferenceParams Bundle request,
@Nullable CancellationSignal cancellationSignal,
@NonNull @CallbackExecutor Executor callbackExecutor,
@NonNull OutcomeReceiver<TokenInfo,
- OnDeviceIntelligenceManagerException> outcomeReceiver) {
+ OnDeviceIntelligenceException> outcomeReceiver) {
try {
ITokenInfoCallback callback = new ITokenInfoCallback.Stub() {
@Override
@@ -331,7 +332,7 @@
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> outcomeReceiver.onError(
- new OnDeviceIntelligenceManagerProcessingException(
+ new OnDeviceIntelligenceException(
errorCode, errorMessage, errorParams))));
}
};
@@ -357,30 +358,30 @@
* failure.
*
* @param feature feature associated with the request.
- * @param request request that contains the Content data and
- * associated params.
+ * @param request request and associated params represented by the Bundle
+ * data.
* @param requestType type of request being sent for processing the content.
* @param cancellationSignal signal to invoke cancellation.
* @param processingSignal signal to send custom signals in the
* remote implementation.
* @param callbackExecutor executor to run the callback on.
- * @param responseCallback callback to populate the response content and
+ * @param processingCallback callback to populate the response content and
* associated params.
*/
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
- public void processRequest(@NonNull Feature feature, @Nullable Content request,
+ public void processRequest(@NonNull Feature feature, @NonNull @InferenceParams Bundle request,
@RequestType int requestType,
@Nullable CancellationSignal cancellationSignal,
@Nullable ProcessingSignal processingSignal,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull ProcessingOutcomeReceiver responseCallback) {
+ @NonNull ProcessingCallback processingCallback) {
try {
IResponseCallback callback = new IResponseCallback.Stub() {
@Override
- public void onSuccess(Content result) {
+ public void onSuccess(@InferenceParams Bundle result) {
Binder.withCleanCallingIdentity(() -> {
- callbackExecutor.execute(() -> responseCallback.onResult(result));
+ callbackExecutor.execute(() -> processingCallback.onResult(result));
});
}
@@ -388,16 +389,16 @@
public void onFailure(int errorCode, String errorMessage,
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
- () -> responseCallback.onError(
- new OnDeviceIntelligenceManagerProcessingException(
+ () -> processingCallback.onError(
+ new OnDeviceIntelligenceException(
errorCode, errorMessage, errorParams))));
}
@Override
- public void onDataAugmentRequest(@NonNull Content content,
+ public void onDataAugmentRequest(@NonNull @InferenceParams Bundle request,
@NonNull RemoteCallback contentCallback) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
- () -> responseCallback.onDataAugmentRequest(content, result -> {
+ () -> processingCallback.onDataAugmentRequest(request, result -> {
Bundle bundle = new Bundle();
bundle.putParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY, result);
callbackExecutor.execute(() -> contentCallback.sendResult(bundle));
@@ -430,15 +431,15 @@
* Variation of {@link #processRequest} that asynchronously processes a request in a
* streaming
* fashion, where new content is pushed to caller in chunks via the
- * {@link StreamedProcessingOutcomeReceiver#onNewContent}. After the streaming is complete,
- * the service should call {@link StreamedProcessingOutcomeReceiver#onResult} and can optionally
- * populate the complete the full response {@link Content} as part of the callback in cases
- * when the final response contains an enhanced aggregation of the Contents already
+ * {@link StreamingProcessingCallback#onPartialResult}. After the streaming is complete,
+ * the service should call {@link StreamingProcessingCallback#onResult} and can optionally
+ * populate the complete the full response {@link Bundle} as part of the callback in cases
+ * when the final response contains an enhanced aggregation of the contents already
* streamed.
*
* @param feature feature associated with the request.
- * @param request request that contains the Content data and associated
- * params.
+ * @param request request and associated params represented by the Bundle
+ * data.
* @param requestType type of request being sent for processing the content.
* @param cancellationSignal signal to invoke cancellation.
* @param processingSignal signal to send custom signals in the
@@ -448,27 +449,27 @@
* @param callbackExecutor executor to run the callback on.
*/
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
- public void processRequestStreaming(@NonNull Feature feature, @Nullable Content request,
+ public void processRequestStreaming(@NonNull Feature feature, @NonNull @InferenceParams Bundle request,
@RequestType int requestType,
@Nullable CancellationSignal cancellationSignal,
@Nullable ProcessingSignal processingSignal,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull StreamedProcessingOutcomeReceiver streamingResponseCallback) {
+ @NonNull StreamingProcessingCallback streamingProcessingCallback) {
try {
IStreamingResponseCallback callback = new IStreamingResponseCallback.Stub() {
@Override
- public void onNewContent(Content result) {
+ public void onNewContent(@InferenceParams Bundle result) {
Binder.withCleanCallingIdentity(() -> {
callbackExecutor.execute(
- () -> streamingResponseCallback.onNewContent(result));
+ () -> streamingProcessingCallback.onPartialResult(result));
});
}
@Override
- public void onSuccess(Content result) {
+ public void onSuccess(@InferenceParams Bundle result) {
Binder.withCleanCallingIdentity(() -> {
callbackExecutor.execute(
- () -> streamingResponseCallback.onResult(result));
+ () -> streamingProcessingCallback.onResult(result));
});
}
@@ -477,18 +478,18 @@
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> {
callbackExecutor.execute(
- () -> streamingResponseCallback.onError(
- new OnDeviceIntelligenceManagerProcessingException(
+ () -> streamingProcessingCallback.onError(
+ new OnDeviceIntelligenceException(
errorCode, errorMessage, errorParams)));
});
}
@Override
- public void onDataAugmentRequest(@NonNull Content content,
+ public void onDataAugmentRequest(@NonNull @InferenceParams Bundle content,
@NonNull RemoteCallback contentCallback) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
- () -> streamingResponseCallback.onDataAugmentRequest(content,
+ () -> streamingProcessingCallback.onDataAugmentRequest(content,
contentResponse -> {
Bundle bundle = new Bundle();
bundle.putParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY,
@@ -519,7 +520,7 @@
}
- /** Request inference with provided Content and Params. */
+ /** Request inference with provided Bundle and Params. */
public static final int REQUEST_TYPE_INFERENCE = 0;
/**
@@ -530,7 +531,7 @@
*/
public static final int REQUEST_TYPE_PREPARE = 1;
- /** Request Embeddings of the passed-in Content. */
+ /** Request Embeddings of the passed-in Bundle. */
public static final int REQUEST_TYPE_EMBEDDINGS = 2;
/**
@@ -547,154 +548,30 @@
public @interface RequestType {
}
-
/**
- * Exception type to be populated in callbacks to the methods under
- * {@link OnDeviceIntelligenceManager}.
+ * {@link Bundle}s annotated with this type will be validated that they are in-effect read-only
+ * when passed to inference service via Binder IPC. Following restrictions apply :
+ * <ul>
+ * <li> Any primitive types or their collections can be added as usual.</li>
+ * <li>IBinder objects should *not* be added.</li>
+ * <li>Parcelable data which has no active-objects, should be added as
+ * {@link Bundle#putByteArray}</li>
+ * <li>Parcelables have active-objects, only following types will be allowed</li>
+ * <ul>
+ * <li>{@link Bitmap} set as {@link Bitmap#setImmutable()}</li>
+ * <li>{@link android.database.CursorWindow}</li>
+ * <li>{@link android.os.ParcelFileDescriptor} opened in
+ * {@link android.os.ParcelFileDescriptor#MODE_READ_ONLY}</li>
+ * <li>{@link android.os.SharedMemory} set to {@link OsConstants#PROT_READ}</li>
+ * </ul>
+ * </ul>
+ *
+ * In all other scenarios the system-server might throw a
+ * {@link android.os.BadParcelableException} if the Bundle validation fails.
+ *
+ * @hide
*/
- public static class OnDeviceIntelligenceManagerException extends Exception {
- /**
- * Error code returned when the OnDeviceIntelligenceManager service is unavailable.
- */
- public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 1000;
-
- /**
- * Error code to be used for on device intelligence manager failures.
- *
- * @hide
- */
- @IntDef(
- value = {
- ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE
- }, open = true)
- @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
- @interface OnDeviceIntelligenceManagerErrorCode {
- }
-
- private final int mErrorCode;
- private final PersistableBundle errorParams;
-
- public OnDeviceIntelligenceManagerException(
- @OnDeviceIntelligenceManagerErrorCode int errorCode, @NonNull String errorMessage,
- @NonNull PersistableBundle errorParams) {
- super(errorMessage);
- this.mErrorCode = errorCode;
- this.errorParams = errorParams;
- }
-
- public OnDeviceIntelligenceManagerException(
- @OnDeviceIntelligenceManagerErrorCode int errorCode,
- @NonNull PersistableBundle errorParams) {
- this.mErrorCode = errorCode;
- this.errorParams = errorParams;
- }
-
- public int getErrorCode() {
- return mErrorCode;
- }
-
- @NonNull
- public PersistableBundle getErrorParams() {
- return errorParams;
- }
- }
-
- /**
- * Exception type to be populated in callbacks to the methods under
- * {@link OnDeviceIntelligenceManager#processRequest} or
- * {@link OnDeviceIntelligenceManager#processRequestStreaming} .
- */
- public static class OnDeviceIntelligenceManagerProcessingException extends
- OnDeviceIntelligenceManagerException {
-
- public static final int PROCESSING_ERROR_UNKNOWN = 1;
-
- /** Request passed contains bad data for e.g. format. */
- public static final int PROCESSING_ERROR_BAD_DATA = 2;
-
- /** Bad request for inputs. */
- public static final int PROCESSING_ERROR_BAD_REQUEST = 3;
-
- /** Whole request was classified as not safe, and no response will be generated. */
- public static final int PROCESSING_ERROR_REQUEST_NOT_SAFE = 4;
-
- /** Underlying processing encountered an error and failed to compute results. */
- public static final int PROCESSING_ERROR_COMPUTE_ERROR = 5;
-
- /** Encountered an error while performing IPC */
- public static final int PROCESSING_ERROR_IPC_ERROR = 6;
-
- /** Request was cancelled either by user signal or by the underlying implementation. */
- public static final int PROCESSING_ERROR_CANCELLED = 7;
-
- /** Underlying processing in the remote implementation is not available. */
- public static final int PROCESSING_ERROR_NOT_AVAILABLE = 8;
-
- /** The service is currently busy. Callers should retry with exponential backoff. */
- public static final int PROCESSING_ERROR_BUSY = 9;
-
- /** Something went wrong with safety classification service. */
- public static final int PROCESSING_ERROR_SAFETY_ERROR = 10;
-
- /** Response generated was classified unsafe. */
- public static final int PROCESSING_ERROR_RESPONSE_NOT_SAFE = 11;
-
- /** Request is too large to be processed. */
- public static final int PROCESSING_ERROR_REQUEST_TOO_LARGE = 12;
-
- /** Inference suspended so that higher-priority inference can run. */
- public static final int PROCESSING_ERROR_SUSPENDED = 13;
-
- /**
- * Underlying processing encountered an internal error, like a violated precondition
- * .
- */
- public static final int PROCESSING_ERROR_INTERNAL = 14;
-
- /**
- * The processing was not able to be passed on to the remote implementation, as the
- * service
- * was unavailable.
- */
- public static final int PROCESSING_ERROR_SERVICE_UNAVAILABLE = 15;
-
- /**
- * Error code of failed processing request.
- *
- * @hide
- */
- @IntDef(
- value = {
- PROCESSING_ERROR_UNKNOWN,
- PROCESSING_ERROR_BAD_DATA,
- PROCESSING_ERROR_BAD_REQUEST,
- PROCESSING_ERROR_REQUEST_NOT_SAFE,
- PROCESSING_ERROR_COMPUTE_ERROR,
- PROCESSING_ERROR_IPC_ERROR,
- PROCESSING_ERROR_CANCELLED,
- PROCESSING_ERROR_NOT_AVAILABLE,
- PROCESSING_ERROR_BUSY,
- PROCESSING_ERROR_SAFETY_ERROR,
- PROCESSING_ERROR_RESPONSE_NOT_SAFE,
- PROCESSING_ERROR_REQUEST_TOO_LARGE,
- PROCESSING_ERROR_SUSPENDED,
- PROCESSING_ERROR_INTERNAL,
- PROCESSING_ERROR_SERVICE_UNAVAILABLE
- }, open = true)
- @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
- @interface ProcessingError {
- }
-
- public OnDeviceIntelligenceManagerProcessingException(
- @ProcessingError int errorCode, @NonNull String errorMessage,
- @NonNull PersistableBundle errorParams) {
- super(errorCode, errorMessage, errorParams);
- }
-
- public OnDeviceIntelligenceManagerProcessingException(
- @ProcessingError int errorCode,
- @NonNull PersistableBundle errorParams) {
- super(errorCode, errorParams);
- }
+ @Target({ElementType.PARAMETER, ElementType.FIELD})
+ public @interface InferenceParams {
}
}
diff --git a/core/java/android/app/ondeviceintelligence/ProcessingCallback.java b/core/java/android/app/ondeviceintelligence/ProcessingCallback.java
new file mode 100644
index 0000000..4d936ea
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/ProcessingCallback.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.InferenceParams;
+
+import java.util.function.Consumer;
+
+/**
+ * Callback to populate the processed response or any error that occurred during the
+ * request processing. This callback also provides a method to request additional data to be
+ * augmented to the request-processing, using the partial response that was already
+ * processed in the remote implementation.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public interface ProcessingCallback {
+ /**
+ * Invoked when request has been processed and result is ready to be propagated to the
+ * caller.
+ *
+ * @param result Response to be passed as a result.
+ */
+ void onResult(@NonNull @InferenceParams Bundle result);
+
+ /**
+ * Called when the request processing fails. The failure details are indicated by the
+ * {@link OnDeviceIntelligenceException} passed as an argument to this method.
+ *
+ * @param error An exception with more details about the error that occurred.
+ */
+ void onError(@NonNull OnDeviceIntelligenceException error);
+
+ /**
+ * Callback to be invoked in cases where the remote service needs to perform retrieval or
+ * transformation operations based on a partially processed request, in order to augment the
+ * final response, by using the additional context sent via this callback.
+ *
+ * @param processedContent The content payload that should be used to augment ongoing request.
+ * @param contentConsumer The augmentation data that should be sent to remote
+ * service for further processing a request. Bundle passed in here is
+ * expected to be non-null or EMPTY when there is no response.
+ */
+ default void onDataAugmentRequest(
+ @NonNull @InferenceParams Bundle processedContent,
+ @NonNull Consumer<Bundle> contentConsumer) {
+ contentConsumer.accept(Bundle.EMPTY);
+ }
+}
diff --git a/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java b/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java
deleted file mode 100644
index b0b6e19..0000000
--- a/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.ondeviceintelligence;
-
-import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
-
-import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
-import android.annotation.SystemApi;
-import android.os.OutcomeReceiver;
-
-import java.util.function.Consumer;
-
-/**
- * Response Callback to populate the processed response or any error that occurred during the
- * request processing. This callback also provides a method to request additional data to be
- * augmented to the request-processing, using the partial {@link Content} that was already
- * processed in the remote implementation.
- *
- * @hide
- */
-@SystemApi
-@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-public interface ProcessingOutcomeReceiver extends
- OutcomeReceiver<Content,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> {
- /**
- * Callback to be invoked in cases where the remote service needs to perform retrieval or
- * transformation operations based on a partially processed request, in order to augment the
- * final response, by using the additional context sent via this callback.
- *
- * @param content The content payload that should be used to augment ongoing request.
- * @param contentConsumer The augmentation data that should be sent to remote
- * service for further processing a request.
- */
- default void onDataAugmentRequest(@NonNull Content content,
- @NonNull Consumer<Content> contentConsumer) {
- contentConsumer.accept(null);
- }
-}
diff --git a/core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java b/core/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java
similarity index 65%
rename from core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java
rename to core/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java
index ac2b032..41f1807 100644
--- a/core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java
+++ b/core/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java
@@ -21,19 +21,21 @@
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.InferenceParams;
/**
- * Streaming variant of outcome receiver to populate response while processing a given request,
- * possibly in chunks to provide a async processing behaviour to the caller.
+ * Streaming variant of {@link ProcessingCallback} to populate response while processing a given
+ * request, possibly in chunks to provide a async processing behaviour to the caller.
*
* @hide
*/
@SystemApi
@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-public interface StreamedProcessingOutcomeReceiver extends ProcessingOutcomeReceiver {
+public interface StreamingProcessingCallback extends ProcessingCallback {
/**
- * Callback that would be invoked when a part of the response i.e. some {@link Content} is
- * already processed and needs to be passed onto the caller.
+ * Callback that would be invoked when a part of the response i.e. some response is
+ * already processed, and needs to be passed onto the caller.
*/
- void onNewContent(@NonNull Content content);
+ void onPartialResult(@NonNull @InferenceParams Bundle partialResult);
}
diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java
index fd72c49..9573e6f 100644
--- a/core/java/android/app/wearable/WearableSensingManager.java
+++ b/core/java/android/app/wearable/WearableSensingManager.java
@@ -95,11 +95,12 @@
/**
* The value of the status code that indicates one or more of the requested events are not
* supported.
+ *
+ * @deprecated WearableSensingManager does not deal with events. Use {@link
+ * STATUS_UNSUPPORTED_OPERATION} instead for operations not supported by the implementation of
+ * {@link WearableSensingService}.
*/
- // TODO(b/324635656): Deprecate this status code. Update Javadoc:
- // @deprecated WearableSensingManager does not deal with events. Use {@link
- // STATUS_UNSUPPORTED_OPERATION} instead for operations not supported by the implementation of
- // {@link WearableSensingService}.
+ @Deprecated
public static final int STATUS_UNSUPPORTED = 2;
/**
@@ -121,7 +122,6 @@
* The value of the status code that indicates the method called is not supported by the
* implementation of {@link WearableSensingService}.
*/
-
@FlaggedApi(Flags.FLAG_ENABLE_UNSUPPORTED_OPERATION_STATUS_CODE)
public static final int STATUS_UNSUPPORTED_OPERATION = 6;
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index cda4d89..2c0e035 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -822,7 +822,18 @@
*
* @param appWidgetIds The AppWidget instances to notify of view data changes.
* @param viewId The collection view id.
+ * @deprecated The corresponding API
+ * {@link RemoteViews#setRemoteAdapter(int, Intent)} associated with this method has been
+ * deprecated. Moving forward please use
+ * {@link RemoteViews#setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)}
+ * instead to set {@link android.widget.RemoteViews.RemoteCollectionItems} for the remote
+ * adapter and update the widget views by calling {@link #updateAppWidget(int[], RemoteViews)},
+ * {@link #updateAppWidget(int, RemoteViews)},
+ * {@link #updateAppWidget(ComponentName, RemoteViews)},
+ * {@link #partiallyUpdateAppWidget(int[], RemoteViews)},
+ * or {@link #partiallyUpdateAppWidget(int, RemoteViews)}, whichever applicable.
*/
+ @Deprecated
public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) {
if (mService == null) {
return;
@@ -873,7 +884,18 @@
*
* @param appWidgetId The AppWidget instance to notify of view data changes.
* @param viewId The collection view id.
+ * @deprecated The corresponding API
+ * {@link RemoteViews#setRemoteAdapter(int, Intent)} associated with this method has been
+ * deprecated. Moving forward please use
+ * {@link RemoteViews#setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)}
+ * instead to set {@link android.widget.RemoteViews.RemoteCollectionItems} for the remote
+ * adapter and update the widget views by calling {@link #updateAppWidget(int[], RemoteViews)},
+ * {@link #updateAppWidget(int, RemoteViews)},
+ * {@link #updateAppWidget(ComponentName, RemoteViews)},
+ * {@link #partiallyUpdateAppWidget(int[], RemoteViews)},
+ * or {@link #partiallyUpdateAppWidget(int, RemoteViews)}, whichever applicable.
*/
+ @Deprecated
public void notifyAppWidgetViewDataChanged(int appWidgetId, int viewId) {
if (mService == null) {
return;
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index a64ee5b..bd04634 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -333,10 +333,9 @@
}
/**
- * Specifies permissions necessary to launch this activity via
- * {@link android.content.Context#startActivity} when passing content URIs. The default value is
- * {@code none}, meaning no specific permissions are required. Setting this attribute restricts
- * activity invocation based on the invoker's permissions.
+ * Specifies permissions necessary to launch this activity when passing content URIs. The
+ * default value is {@code none}, meaning no specific permissions are required. Setting this
+ * attribute restricts activity invocation based on the invoker's permissions.
* @hide
*/
@RequiredContentUriPermission
@@ -1519,6 +1518,16 @@
private static final long CHECK_MIN_WIDTH_HEIGHT_FOR_MULTI_WINDOW = 197654537L;
/**
+ * The activity is targeting a SDK version that should receive the changed behavior of
+ * configuration insets decouple.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public static final long INSETS_DECOUPLED_CONFIGURATION_ENFORCED = 151861875L;
+
+ /**
* Optional set of a certificates identifying apps that are allowed to embed this activity. From
* the "knownActivityEmbeddingCerts" attribute.
*/
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index 533fa51..55957bf 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -133,4 +133,7 @@
void setArchiveCompatibilityOptions(boolean enableIconOverlay, boolean enableUnarchivalConfirmation);
List<UserHandle> getUserProfiles();
+
+ /** Saves view capture data to the wm trace directory. */
+ void saveViewCaptureData();
}
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 41c1f17..3a5383d 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -1328,6 +1328,19 @@
}
/**
+ * Saves view capture data to the default location.
+ * @hide
+ */
+ @RequiresPermission(READ_FRAME_BUFFER)
+ public void saveViewCaptureData() {
+ try {
+ mService.saveViewCaptureData();
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
* Unregister a callback, so that it won't be called when LauncherApps dumps.
* @hide
*/
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index f660770..7fd0b03 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -38,7 +38,7 @@
name: "nine_patch_frro"
namespace: "resource_manager"
description: "Feature flag for creating an frro from a 9-patch"
- bug: "309232726"
+ bug: "296324826"
}
flag {
diff --git a/core/java/android/content/rollback/OWNERS b/core/java/android/content/rollback/OWNERS
index 8e5a0d8..c328b7c 100644
--- a/core/java/android/content/rollback/OWNERS
+++ b/core/java/android/content/rollback/OWNERS
@@ -1,5 +1,3 @@
# Bug component: 819107
-ancr@google.com
-harshitmahajan@google.com
-robertogil@google.com
+include /services/core/java/com/android/server/crashrecovery/OWNERS
\ No newline at end of file
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index dc8f4b4..238c381 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -6098,7 +6098,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<android.util.Range<Float>> EFV_PADDING_ZOOM_FACTOR_RANGE =
new Key<android.util.Range<Float>>("android.efv.paddingZoomFactorRange", new TypeReference<android.util.Range<Float>>() {{ }});
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 18a33eb..6962811 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -142,7 +142,7 @@
/**
* An extension that aims to lock and stabilize a given region or object of interest.
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final int EXTENSION_EYES_FREE_VIDEOGRAPHY = 5;
/**
@@ -563,7 +563,7 @@
public ExtensionConnectionManager() {
IntArray extensionList = new IntArray(EXTENSION_LIST.length);
extensionList.addAll(EXTENSION_LIST);
- if (Flags.concertMode()) {
+ if (Flags.concertModeApi()) {
extensionList.add(EXTENSION_EYES_FREE_VIDEOGRAPHY);
}
@@ -766,7 +766,7 @@
IntArray extensionList = new IntArray(EXTENSION_LIST.length);
extensionList.addAll(EXTENSION_LIST);
- if (Flags.concertMode()) {
+ if (Flags.concertModeApi()) {
extensionList.add(EXTENSION_EYES_FREE_VIDEOGRAPHY);
}
@@ -1548,7 +1548,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<android.util.Range<Float>> EFV_PADDING_ZOOM_FACTOR_RANGE =
CameraCharacteristics.EFV_PADDING_ZOOM_FACTOR_RANGE;
}
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 9fb561b..7754e32 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -3905,7 +3905,7 @@
* @see CaptureRequest#EFV_STABILIZATION_MODE
* @hide
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final int EFV_STABILIZATION_MODE_OFF = 0;
/**
@@ -3913,7 +3913,7 @@
* @see CaptureRequest#EFV_STABILIZATION_MODE
* @hide
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final int EFV_STABILIZATION_MODE_GIMBAL = 1;
/**
@@ -3923,7 +3923,7 @@
* @see CaptureRequest#EFV_STABILIZATION_MODE
* @hide
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final int EFV_STABILIZATION_MODE_LOCKED = 2;
//
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index c0db77c..13d5c7e 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -4335,7 +4335,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_PADDING_ZOOM_FACTOR =
new Key<Float>("android.efv.paddingZoomFactor", float.class);
@@ -4358,7 +4358,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Boolean> EFV_AUTO_ZOOM =
new Key<Boolean>("android.efv.autoZoom", boolean.class);
@@ -4379,7 +4379,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR =
new Key<Float>("android.efv.maxPaddingZoomFactor", float.class);
@@ -4406,7 +4406,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Integer> EFV_STABILIZATION_MODE =
new Key<Integer>("android.efv.stabilizationMode", int.class);
@@ -4428,7 +4428,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT =
new Key<android.util.Pair<Integer,Integer>>("android.efv.translateViewport", new TypeReference<android.util.Pair<Integer,Integer>>() {{ }});
@@ -4445,7 +4445,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_ROTATE_VIEWPORT =
new Key<Float>("android.efv.rotateViewport", float.class);
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index a01c23d..7145501 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -5940,7 +5940,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<int[]> EFV_PADDING_REGION =
new Key<int[]>("android.efv.paddingRegion", int[].class);
@@ -5961,7 +5961,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<int[]> EFV_AUTO_ZOOM_PADDING_REGION =
new Key<int[]>("android.efv.autoZoomPaddingRegion", int[].class);
@@ -5984,7 +5984,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<android.graphics.PointF[]> EFV_TARGET_COORDINATES =
new Key<android.graphics.PointF[]>("android.efv.targetCoordinates", android.graphics.PointF[].class);
@@ -6014,7 +6014,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_PADDING_ZOOM_FACTOR =
new Key<Float>("android.efv.paddingZoomFactor", float.class);
@@ -6041,7 +6041,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Integer> EFV_STABILIZATION_MODE =
new Key<Integer>("android.efv.stabilizationMode", int.class);
@@ -6064,7 +6064,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Boolean> EFV_AUTO_ZOOM =
new Key<Boolean>("android.efv.autoZoom", boolean.class);
@@ -6081,7 +6081,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_ROTATE_VIEWPORT =
new Key<Float>("android.efv.rotateViewport", float.class);
@@ -6103,7 +6103,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT =
new Key<android.util.Pair<Integer,Integer>>("android.efv.translateViewport", new TypeReference<android.util.Pair<Integer,Integer>>() {{ }});
@@ -6124,7 +6124,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR =
new Key<Float>("android.efv.maxPaddingZoomFactor", float.class);
diff --git a/core/java/android/hardware/camera2/ExtensionCaptureRequest.java b/core/java/android/hardware/camera2/ExtensionCaptureRequest.java
index 32039c6..c33956b 100644
--- a/core/java/android/hardware/camera2/ExtensionCaptureRequest.java
+++ b/core/java/android/hardware/camera2/ExtensionCaptureRequest.java
@@ -40,7 +40,7 @@
* @see CaptureRequest
* @see CameraExtensionSession
*/
-@FlaggedApi(Flags.FLAG_CONCERT_MODE)
+@FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public final class ExtensionCaptureRequest {
/**
@@ -74,7 +74,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_PADDING_ZOOM_FACTOR = CaptureRequest.EFV_PADDING_ZOOM_FACTOR;
/**
@@ -99,7 +99,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Boolean> EFV_AUTO_ZOOM = CaptureRequest.EFV_AUTO_ZOOM;
/**
@@ -125,7 +125,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR = CaptureRequest.EFV_MAX_PADDING_ZOOM_FACTOR;
/**
@@ -152,7 +152,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Integer> EFV_STABILIZATION_MODE = CaptureRequest.EFV_STABILIZATION_MODE;
/**
@@ -176,7 +176,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT = CaptureRequest.EFV_TRANSLATE_VIEWPORT;
/**
@@ -193,7 +193,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_ROTATE_VIEWPORT = CaptureRequest.EFV_ROTATE_VIEWPORT;
@@ -205,14 +205,14 @@
* <p>No stabilization.</p>
* @see ExtensionCaptureRequest#EFV_STABILIZATION_MODE
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final int EFV_STABILIZATION_MODE_OFF = CaptureRequest.EFV_STABILIZATION_MODE_OFF;
/**
* <p>Gimbal stabilization mode.</p>
* @see ExtensionCaptureRequest#EFV_STABILIZATION_MODE
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final int EFV_STABILIZATION_MODE_GIMBAL = CaptureRequest.EFV_STABILIZATION_MODE_GIMBAL;
/**
@@ -221,7 +221,7 @@
* stabilization to directionally steady the target region.</p>
* @see ExtensionCaptureRequest#EFV_STABILIZATION_MODE
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final int EFV_STABILIZATION_MODE_LOCKED = CaptureRequest.EFV_STABILIZATION_MODE_LOCKED;
-}
\ No newline at end of file
+}
diff --git a/core/java/android/hardware/camera2/ExtensionCaptureResult.java b/core/java/android/hardware/camera2/ExtensionCaptureResult.java
index 5c99909..95feb2f 100644
--- a/core/java/android/hardware/camera2/ExtensionCaptureResult.java
+++ b/core/java/android/hardware/camera2/ExtensionCaptureResult.java
@@ -42,7 +42,7 @@
* @see CaptureRequest
* @see CameraExtensionSession
*/
-@FlaggedApi(Flags.FLAG_CONCERT_MODE)
+@FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public final class ExtensionCaptureResult {
/**
@@ -66,7 +66,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<int[]> EFV_PADDING_REGION = CaptureResult.EFV_PADDING_REGION;
/**
@@ -90,7 +90,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<int[]> EFV_AUTO_ZOOM_PADDING_REGION = CaptureResult.EFV_AUTO_ZOOM_PADDING_REGION;
/**
@@ -113,7 +113,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<android.graphics.PointF[]> EFV_TARGET_COORDINATES = CaptureResult.EFV_TARGET_COORDINATES;
/**
@@ -147,7 +147,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_PADDING_ZOOM_FACTOR = CaptureResult.EFV_PADDING_ZOOM_FACTOR;
/**
@@ -174,7 +174,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Integer> EFV_STABILIZATION_MODE = CaptureResult.EFV_STABILIZATION_MODE;
/**
@@ -199,7 +199,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Boolean> EFV_AUTO_ZOOM = CaptureResult.EFV_AUTO_ZOOM;
/**
@@ -216,7 +216,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_ROTATE_VIEWPORT = CaptureResult.EFV_ROTATE_VIEWPORT;
/**
@@ -240,7 +240,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT = CaptureResult.EFV_TRANSLATE_VIEWPORT;
/**
@@ -266,7 +266,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR = CaptureResult.EFV_MAX_PADDING_ZOOM_FACTOR;
-}
\ No newline at end of file
+}
diff --git a/core/java/android/hardware/devicestate/DeviceState.java b/core/java/android/hardware/devicestate/DeviceState.java
index 905d911..76888f3 100644
--- a/core/java/android/hardware/devicestate/DeviceState.java
+++ b/core/java/android/hardware/devicestate/DeviceState.java
@@ -543,11 +543,13 @@
int identifier = source.readInt();
String name = source.readString8();
ArraySet<@DeviceStateProperties Integer> systemProperties = new ArraySet<>();
- for (int i = 0; i < source.readInt(); i++) {
+ int systemPropertySize = source.readInt();
+ for (int i = 0; i < systemPropertySize; i++) {
systemProperties.add(source.readInt());
}
ArraySet<@DeviceStateProperties Integer> physicalProperties = new ArraySet<>();
- for (int j = 0; j < source.readInt(); j++) {
+ int physicalPropertySize = source.readInt();
+ for (int j = 0; j < physicalPropertySize; j++) {
physicalProperties.add(source.readInt());
}
return new DeviceState.Configuration(identifier, name, systemProperties,
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
index a3a2a2e..c5167db 100644
--- a/core/java/android/hardware/radio/ProgramList.java
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -304,7 +304,11 @@
*
* @param id primary identifier of a program to fetch
* @return the program info, or null if there is no such program on the list
+ *
+ * @deprecated Use {@link #getProgramInfos(ProgramSelector.Identifier)} to get all programs
+ * with the given primary identifier
*/
+ @Deprecated
public @Nullable RadioManager.ProgramInfo get(@NonNull ProgramSelector.Identifier id) {
Map<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries;
synchronized (mLock) {
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index a968c6f..0740374 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -312,15 +312,23 @@
public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10;
/**
* 1: AM, 2:FM
+ * @deprecated use {@link #IDENTIFIER_TYPE_DRMO_FREQUENCY} instead
*/
+ @Deprecated
public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11;
/**
* 32bit primary identifier for SiriusXM Satellite Radio.
+ *
+ * @deprecated SiriusXM Satellite Radio is not supported
*/
+ @Deprecated
public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12;
/**
* 0-999 range
+ *
+ * @deprecated SiriusXM Satellite Radio is not supported
*/
+ @Deprecated
public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13;
/**
* 44bit compound primary identifier for Digital Audio Broadcasting and
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 61cf8901..da6c686 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -166,7 +166,12 @@
* analog handover state managed from the HAL implementation side.
*
* <p>Some radio technologies may not support this, i.e. DAB.
+ *
+ * @deprecated Use {@link #CONFIG_FORCE_ANALOG_FM} instead. If {@link #CONFIG_FORCE_ANALOG_FM}
+ * is supported in HAL, {@link RadioTuner#setConfigFlag} and {@link RadioTuner#isConfigFlagSet}
+ * with CONFIG_FORCE_ANALOG will set/get the value of {@link #CONFIG_FORCE_ANALOG_FM}.
*/
+ @Deprecated
public static final int CONFIG_FORCE_ANALOG = 2;
/**
* Forces the digital playback for the supporting radio technology.
diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl
index dd8b3de..c489c58 100644
--- a/core/java/android/service/dreams/IDreamManager.aidl
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -38,6 +38,7 @@
boolean isDreaming();
@UnsupportedAppUsage
boolean isDreamingOrInPreview();
+ boolean canStartDreaming(boolean isScreenOn);
void finishSelf(in IBinder token, boolean immediate);
void startDozing(in IBinder token, int screenState, int screenBrightness);
void stopDozing(in IBinder token);
diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl b/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
index 73257ed..799c7545 100644
--- a/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
+++ b/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
@@ -20,7 +20,6 @@
import android.app.ondeviceintelligence.IResponseCallback;
import android.app.ondeviceintelligence.ITokenInfoCallback;
import android.app.ondeviceintelligence.IProcessingSignal;
-import android.app.ondeviceintelligence.Content;
import android.app.ondeviceintelligence.Feature;
import android.os.ICancellationSignal;
import android.os.PersistableBundle;
@@ -35,12 +34,12 @@
*/
oneway interface IOnDeviceSandboxedInferenceService {
void registerRemoteStorageService(in IRemoteStorageService storageService);
- void requestTokenInfo(int callerUid, in Feature feature, in Content request, in ICancellationSignal cancellationSignal,
+ void requestTokenInfo(int callerUid, in Feature feature, in Bundle request, in ICancellationSignal cancellationSignal,
in ITokenInfoCallback tokenInfoCallback);
- void processRequest(int callerUid, in Feature feature, in Content request, in int requestType,
+ void processRequest(int callerUid, in Feature feature, in Bundle request, in int requestType,
in ICancellationSignal cancellationSignal, in IProcessingSignal processingSignal,
in IResponseCallback callback);
- void processRequestStreaming(int callerUid, in Feature feature, in Content request, in int requestType,
+ void processRequestStreaming(int callerUid, in Feature feature, in Bundle request, in int requestType,
in ICancellationSignal cancellationSignal, in IProcessingSignal processingSignal,
in IStreamingResponseCallback callback);
void updateProcessingState(in Bundle processingState,
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
index fce3689..27e8628 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
@@ -32,7 +32,9 @@
import android.app.ondeviceintelligence.IFeatureCallback;
import android.app.ondeviceintelligence.IFeatureDetailsCallback;
import android.app.ondeviceintelligence.IListFeaturesCallback;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceException;
import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.InferenceParams;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
@@ -48,14 +50,8 @@
import com.android.internal.infra.AndroidFuture;
-import androidx.annotation.IntDef;
-
import java.io.File;
import java.io.FileNotFoundException;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -232,9 +228,9 @@
* @param callbackExecutor executor to the run status callback on.
* @param statusReceiver receiver to get status of the update state operation.
*/
- public final void updateProcessingState(@NonNull Bundle processingState,
+ public final void updateProcessingState(@NonNull @InferenceParams Bundle processingState,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull OutcomeReceiver<PersistableBundle, OnDeviceUpdateProcessingException> statusReceiver) {
+ @NonNull OutcomeReceiver<PersistableBundle, OnDeviceIntelligenceException> statusReceiver) {
Objects.requireNonNull(callbackExecutor);
if (mRemoteProcessingService == null) {
throw new IllegalStateException("Remote processing service is unavailable.");
@@ -254,7 +250,7 @@
public void onFailure(int errorCode, String errorMessage) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> statusReceiver.onError(
- new OnDeviceUpdateProcessingException(
+ new OnDeviceIntelligenceException(
errorCode, errorMessage))));
}
});
@@ -265,7 +261,7 @@
}
private OutcomeReceiver<Feature,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapFeatureCallback(
+ OnDeviceIntelligenceException> wrapFeatureCallback(
IFeatureCallback featureCallback) {
return new OutcomeReceiver<>() {
@Override
@@ -279,7 +275,7 @@
@Override
public void onError(
- @NonNull OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) {
+ @NonNull OnDeviceIntelligenceException exception) {
try {
featureCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
exception.getErrorParams());
@@ -291,7 +287,7 @@
}
private OutcomeReceiver<List<Feature>,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapListFeaturesCallback(
+ OnDeviceIntelligenceException> wrapListFeaturesCallback(
IListFeaturesCallback listFeaturesCallback) {
return new OutcomeReceiver<>() {
@Override
@@ -305,7 +301,7 @@
@Override
public void onError(
- @NonNull OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) {
+ @NonNull OnDeviceIntelligenceException exception) {
try {
listFeaturesCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
exception.getErrorParams());
@@ -317,7 +313,7 @@
}
private OutcomeReceiver<FeatureDetails,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapFeatureDetailsCallback(
+ OnDeviceIntelligenceException> wrapFeatureDetailsCallback(
IFeatureDetailsCallback featureStatusCallback) {
return new OutcomeReceiver<>() {
@Override
@@ -331,7 +327,7 @@
@Override
public void onError(
- @NonNull OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) {
+ @NonNull OnDeviceIntelligenceException exception) {
try {
featureStatusCallback.onFailure(exception.getErrorCode(),
exception.getMessage(), exception.getErrorParams());
@@ -444,7 +440,7 @@
*/
public abstract void onGetFeatureDetails(int callerUid, @NonNull Feature feature,
@NonNull OutcomeReceiver<FeatureDetails,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureDetailsCallback);
+ OnDeviceIntelligenceException> featureDetailsCallback);
/**
@@ -455,7 +451,7 @@
*/
public abstract void onGetFeature(int callerUid, int featureId,
@NonNull OutcomeReceiver<Feature,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureCallback);
+ OnDeviceIntelligenceException> featureCallback);
/**
* List all features which are available in the remote implementation. The implementation might
@@ -465,7 +461,7 @@
* @param listFeaturesCallback callback to populate the features list.
*/
public abstract void onListFeatures(int callerUid, @NonNull OutcomeReceiver<List<Feature>,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> listFeaturesCallback);
+ OnDeviceIntelligenceException> listFeaturesCallback);
/**
* Provides a long value representing the version of the remote implementation processing
@@ -474,60 +470,4 @@
* @param versionConsumer consumer to populate the version.
*/
public abstract void onGetVersion(@NonNull LongConsumer versionConsumer);
-
-
- /**
- * Exception type to be populated when calls to {@link #updateProcessingState} fail.
- */
- public static class OnDeviceUpdateProcessingException extends
- OnDeviceIntelligenceServiceException {
- /**
- * The connection to remote service failed and the processing state could not be updated.
- */
- public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 1;
-
-
- /**
- * @hide
- */
- @IntDef(value = {
- PROCESSING_UPDATE_STATUS_CONNECTION_FAILED
- }, open = true)
- @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER,
- ElementType.FIELD})
- @Retention(RetentionPolicy.SOURCE)
- public @interface ErrorCode {
- }
-
- public OnDeviceUpdateProcessingException(@ErrorCode int errorCode) {
- super(errorCode);
- }
-
- public OnDeviceUpdateProcessingException(@ErrorCode int errorCode,
- @NonNull String errorMessage) {
- super(errorCode, errorMessage);
- }
- }
-
- /**
- * Exception type to be used for surfacing errors to service implementation.
- */
- public abstract static class OnDeviceIntelligenceServiceException extends Exception {
- private final int mErrorCode;
-
- public OnDeviceIntelligenceServiceException(int errorCode) {
- this.mErrorCode = errorCode;
- }
-
- public OnDeviceIntelligenceServiceException(int errorCode,
- @NonNull String errorMessage) {
- super(errorMessage);
- this.mErrorCode = errorCode;
- }
-
- public int getErrorCode() {
- return mErrorCode;
- }
-
- }
}
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
index 7f7f9c2..d943c80 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
@@ -27,20 +27,21 @@
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.Service;
-import android.app.ondeviceintelligence.Content;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceException;
+import android.os.Bundle;
import android.app.ondeviceintelligence.Feature;
import android.app.ondeviceintelligence.IProcessingSignal;
import android.app.ondeviceintelligence.IResponseCallback;
import android.app.ondeviceintelligence.IStreamingResponseCallback;
import android.app.ondeviceintelligence.ITokenInfoCallback;
import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.InferenceParams;
import android.app.ondeviceintelligence.ProcessingSignal;
-import android.app.ondeviceintelligence.ProcessingOutcomeReceiver;
-import android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver;
+import android.app.ondeviceintelligence.ProcessingCallback;
+import android.app.ondeviceintelligence.StreamingProcessingCallback;
import android.app.ondeviceintelligence.TokenInfo;
import android.content.Context;
import android.content.Intent;
-import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.HandlerExecutor;
@@ -51,7 +52,6 @@
import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.RemoteException;
-import android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException;
import android.util.Log;
import android.util.Slog;
@@ -121,7 +121,7 @@
}
@Override
- public void requestTokenInfo(int callerUid, Feature feature, Content request,
+ public void requestTokenInfo(int callerUid, Feature feature, Bundle request,
ICancellationSignal cancellationSignal,
ITokenInfoCallback tokenInfoCallback) {
Objects.requireNonNull(feature);
@@ -134,7 +134,7 @@
}
@Override
- public void processRequestStreaming(int callerUid, Feature feature, Content request,
+ public void processRequestStreaming(int callerUid, Feature feature, Bundle request,
int requestType, ICancellationSignal cancellationSignal,
IProcessingSignal processingSignal,
IStreamingResponseCallback callback) {
@@ -151,7 +151,7 @@
}
@Override
- public void processRequest(int callerUid, Feature feature, Content request,
+ public void processRequest(int callerUid, Feature feature, Bundle request,
int requestType, ICancellationSignal cancellationSignal,
IProcessingSignal processingSignal,
IResponseCallback callback) {
@@ -198,18 +198,17 @@
@NonNull
public abstract void onTokenInfoRequest(
int callerUid, @NonNull Feature feature,
- @NonNull Content request,
+ @NonNull @InferenceParams Bundle request,
@Nullable CancellationSignal cancellationSignal,
- @NonNull OutcomeReceiver<TokenInfo,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback);
+ @NonNull OutcomeReceiver<TokenInfo, OnDeviceIntelligenceException> callback);
/**
* Invoked when caller provides a request for a particular feature to be processed in a
* streaming manner. The expectation from the implementation is that when processing the
* request,
- * it periodically populates the {@link StreamedProcessingOutcomeReceiver#onNewContent} to continuously
- * provide partial Content results for the caller to utilize. Optionally the implementation can
- * provide the complete response in the {@link StreamedProcessingOutcomeReceiver#onResult} upon
+ * it periodically populates the {@link StreamingProcessingCallback#onPartialResult} to continuously
+ * provide partial Bundle results for the caller to utilize. Optionally the implementation can
+ * provide the complete response in the {@link StreamingProcessingCallback#onResult} upon
* processing completion.
*
* @param callerUid UID of the caller that initiated this call chain.
@@ -225,11 +224,11 @@
@NonNull
public abstract void onProcessRequestStreaming(
int callerUid, @NonNull Feature feature,
- @Nullable Content request,
+ @NonNull @InferenceParams Bundle request,
@OnDeviceIntelligenceManager.RequestType int requestType,
@Nullable CancellationSignal cancellationSignal,
@Nullable ProcessingSignal processingSignal,
- @NonNull StreamedProcessingOutcomeReceiver callback);
+ @NonNull StreamingProcessingCallback callback);
/**
* Invoked when caller provides a request for a particular feature to be processed in one shot
@@ -251,11 +250,11 @@
@NonNull
public abstract void onProcessRequest(
int callerUid, @NonNull Feature feature,
- @Nullable Content request,
+ @NonNull @InferenceParams Bundle request,
@OnDeviceIntelligenceManager.RequestType int requestType,
@Nullable CancellationSignal cancellationSignal,
@Nullable ProcessingSignal processingSignal,
- @NonNull ProcessingOutcomeReceiver callback);
+ @NonNull ProcessingCallback callback);
/**
@@ -267,9 +266,9 @@
* @param callback callback to populate the update status and if there are params
* associated with the status.
*/
- public abstract void onUpdateProcessingState(@NonNull Bundle processingState,
+ public abstract void onUpdateProcessingState(@NonNull @InferenceParams Bundle processingState,
@NonNull OutcomeReceiver<PersistableBundle,
- OnDeviceUpdateProcessingException> callback);
+ OnDeviceIntelligenceException> callback);
/**
@@ -344,7 +343,7 @@
/**
* Returns the {@link Executor} to use for incoming IPC from request sender into your service
* implementation. For e.g. see
- * {@link ProcessingOutcomeReceiver#onDataAugmentRequest(Content,
+ * {@link ProcessingCallback#onDataAugmentRequest(Bundle,
* Consumer)} where we use the executor to populate the consumer.
* <p>
* Override this method in your {@link OnDeviceSandboxedInferenceService} implementation to
@@ -380,13 +379,13 @@
});
}
- private ProcessingOutcomeReceiver wrapResponseCallback(
+ private ProcessingCallback wrapResponseCallback(
IResponseCallback callback) {
- return new ProcessingOutcomeReceiver() {
+ return new ProcessingCallback() {
@Override
- public void onResult(@androidx.annotation.NonNull Content response) {
+ public void onResult(@androidx.annotation.NonNull Bundle result) {
try {
- callback.onSuccess(response);
+ callback.onSuccess(result);
} catch (RemoteException e) {
Slog.e(TAG, "Error sending result: " + e);
}
@@ -394,7 +393,7 @@
@Override
public void onError(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) {
+ OnDeviceIntelligenceException exception) {
try {
callback.onFailure(exception.getErrorCode(), exception.getMessage(),
exception.getErrorParams());
@@ -404,8 +403,8 @@
}
@Override
- public void onDataAugmentRequest(@NonNull Content content,
- @NonNull Consumer<Content> contentCallback) {
+ public void onDataAugmentRequest(@NonNull Bundle content,
+ @NonNull Consumer<Bundle> contentCallback) {
try {
callback.onDataAugmentRequest(content, wrapRemoteCallback(contentCallback));
@@ -416,22 +415,22 @@
};
}
- private StreamedProcessingOutcomeReceiver wrapStreamingResponseCallback(
+ private StreamingProcessingCallback wrapStreamingResponseCallback(
IStreamingResponseCallback callback) {
- return new StreamedProcessingOutcomeReceiver() {
+ return new StreamingProcessingCallback() {
@Override
- public void onNewContent(@androidx.annotation.NonNull Content content) {
+ public void onPartialResult(@androidx.annotation.NonNull Bundle partialResult) {
try {
- callback.onNewContent(content);
+ callback.onNewContent(partialResult);
} catch (RemoteException e) {
Slog.e(TAG, "Error sending result: " + e);
}
}
@Override
- public void onResult(@androidx.annotation.NonNull Content response) {
+ public void onResult(@androidx.annotation.NonNull Bundle result) {
try {
- callback.onSuccess(response);
+ callback.onSuccess(result);
} catch (RemoteException e) {
Slog.e(TAG, "Error sending result: " + e);
}
@@ -439,7 +438,7 @@
@Override
public void onError(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) {
+ OnDeviceIntelligenceException exception) {
try {
callback.onFailure(exception.getErrorCode(), exception.getMessage(),
exception.getErrorParams());
@@ -449,8 +448,8 @@
}
@Override
- public void onDataAugmentRequest(@NonNull Content content,
- @NonNull Consumer<Content> contentCallback) {
+ public void onDataAugmentRequest(@NonNull Bundle content,
+ @NonNull Consumer<Bundle> contentCallback) {
try {
callback.onDataAugmentRequest(content, wrapRemoteCallback(contentCallback));
@@ -462,13 +461,13 @@
}
private RemoteCallback wrapRemoteCallback(
- @androidx.annotation.NonNull Consumer<Content> contentCallback) {
+ @androidx.annotation.NonNull Consumer<Bundle> contentCallback) {
return new RemoteCallback(
result -> {
if (result != null) {
getCallbackExecutor().execute(() -> contentCallback.accept(
result.getParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY,
- Content.class)));
+ Bundle.class)));
} else {
getCallbackExecutor().execute(
() -> contentCallback.accept(null));
@@ -476,8 +475,7 @@
});
}
- private OutcomeReceiver<TokenInfo,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapTokenInfoCallback(
+ private OutcomeReceiver<TokenInfo, OnDeviceIntelligenceException> wrapTokenInfoCallback(
ITokenInfoCallback tokenInfoCallback) {
return new OutcomeReceiver<>() {
@Override
@@ -491,7 +489,7 @@
@Override
public void onError(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) {
+ OnDeviceIntelligenceException exception) {
try {
tokenInfoCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
exception.getErrorParams());
@@ -503,7 +501,7 @@
}
@NonNull
- private static OutcomeReceiver<PersistableBundle, OnDeviceUpdateProcessingException> wrapOutcomeReceiver(
+ private static OutcomeReceiver<PersistableBundle, OnDeviceIntelligenceException> wrapOutcomeReceiver(
IProcessingUpdateStatusCallback callback) {
return new OutcomeReceiver<>() {
@Override
@@ -518,7 +516,7 @@
@Override
public void onError(
- @androidx.annotation.NonNull OnDeviceUpdateProcessingException error) {
+ @androidx.annotation.NonNull OnDeviceIntelligenceException error) {
try {
callback.onFailure(error.getErrorCode(), error.getMessage());
} catch (RemoteException e) {
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index a08264e..ccc17ec 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -819,19 +819,15 @@
/**
* Called when the keyphrase is spoken.
*
- * <p>This implicitly stops listening for the keyphrase once it's detected. Clients should
- * start a recognition again once they are done handling this detection.
+ * <p>If {@code eventPayload.isRecognitionStopped()} returns true, this implicitly stops
+ * listening for the keyphrase once it's detected. Clients should start a recognition again
+ * once they are done handling this detection.
*
* @param eventPayload Payload data for the detection event. This may contain the trigger
* audio, if requested when calling {@link
- * AlwaysOnHotwordDetector#startRecognition(int)}.
+ * AlwaysOnHotwordDetector#startRecognition(int)} or if the audio comes from the {@link
+ * android.service.wearable.WearableSensingService}.
*/
- // TODO(b/324635656): Update Javadoc for 24Q3 release:
- // 1. Prepend to the first paragraph:
- // If {@code eventPayload.isRecognitionStopped()} returns true, this...
- // 2. Append to the description for @param eventPayload:
- // ...or if the audio comes from {@link
- // android.service.wearable.WearableSensingService}.
public abstract void onDetected(@NonNull EventPayload eventPayload);
/**
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index 60e9de7..937aecc 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -363,9 +363,11 @@
* {@link HotwordDetector#startRecognition(ParcelFileDescriptor, AudioFormat,
* PersistableBundle)} run} hotword recognition on audio coming from an external connected
* microphone.
- * <p>
- * Upon invoking the {@code callback}, the system closes {@code audioStream} and sends the
- * detection result to the {@link HotwordDetector.Callback hotword detector}.
+ *
+ * <p>Upon invoking the {@code callback}, the system will send the detection result to
+ * the {@link HotwordDetector}'s callback. If {@code
+ * options.getBoolean(KEY_SYSTEM_WILL_CLOSE_AUDIO_STREAM_AFTER_CALLBACK, true)} returns true,
+ * the system will also close the {@code audioStream} after {@code callback} is invoked.
*
* @param audioStream Stream containing audio bytes returned from a microphone
* @param audioFormat Format of the supplied audio
@@ -375,11 +377,6 @@
* PersistableBundle)}.
* @param callback The callback to use for responding to the detection request.
*/
- // TODO(b/324635656): Update Javadoc for 24Q3 release. Change the last paragraph to:
- // <p>Upon invoking the {@code callback}, the system will send the detection result to
- // the {@link HotwordDetector}'s callback. If {@code
- // options.getBoolean(KEY_SYSTEM_WILL_CLOSE_AUDIO_STREAM_AFTER_CALLBACK, true)} returns true,
- // the system will also close the {@code audioStream} after {@code callback} is invoked.
public void onDetect(
@NonNull ParcelFileDescriptor audioStream,
@NonNull AudioFormat audioFormat,
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index f68fcab9..aff1d4a 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -119,3 +119,10 @@
is_fixed_read_only: true
bug: "324676775"
}
+
+flag {
+ name: "handwriting_cursor_position"
+ namespace: "text"
+ description: "When handwriting is initiated in an unfocused TextView, cursor is placed at the end of the closest paragraph."
+ bug: "323376217"
+}
diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java
index a6724da..a4c3ed9 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -24,6 +24,7 @@
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import static android.util.apk.ApkSignatureSchemeV4Verifier.APK_SIGNATURE_SCHEME_DEFAULT;
+import android.annotation.NonNull;
import android.content.pm.Signature;
import android.content.pm.SigningDetails;
import android.content.pm.SigningDetails.SignatureSchemeVersion;
@@ -33,9 +34,12 @@
import android.os.Build;
import android.os.Trace;
import android.os.incremental.V4Signature;
+import android.util.ArrayMap;
import android.util.Pair;
+import android.util.Slog;
import android.util.jar.StrictJarFile;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import libcore.io.IoUtils;
@@ -63,8 +67,14 @@
*/
public class ApkSignatureVerifier {
+ private static final String LOG_TAG = "ApkSignatureVerifier";
+
private static final AtomicReference<byte[]> sBuffer = new AtomicReference<>();
+ @GuardedBy("sOverrideSigningDetails")
+ private static final ArrayMap<SigningDetails, SigningDetails> sOverrideSigningDetails =
+ new ArrayMap<>();
+
/**
* Verifies the provided APK and returns the certificates associated with each signer.
*/
@@ -95,7 +105,54 @@
if (result.isError()) {
return input.error(result);
}
- return input.success(result.getResult().signingDetails);
+ SigningDetails signingDetails = result.getResult().signingDetails;
+ if (Build.isDebuggable()) {
+ SigningDetails overrideSigningDetails;
+ synchronized (sOverrideSigningDetails) {
+ overrideSigningDetails = sOverrideSigningDetails.get(signingDetails);
+ }
+ if (overrideSigningDetails != null) {
+ Slog.i(LOG_TAG, "Applying override signing details for APK " + apkPath);
+ signingDetails = overrideSigningDetails;
+ }
+ }
+ return input.success(signingDetails);
+ }
+
+ /**
+ * Add a pair of signing details so that packages signed with {@code oldSigningDetails} will
+ * behave as if they are signed by the {@code newSigningDetails}.
+ *
+ * @param oldSigningDetails the original signing detail of the package
+ * @param newSigningDetails the new signing detail that will replace the original one
+ */
+ public static void addOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails,
+ @NonNull SigningDetails newSigningDetails) {
+ synchronized (sOverrideSigningDetails) {
+ sOverrideSigningDetails.put(oldSigningDetails, newSigningDetails);
+ }
+ }
+
+ /**
+ * Remove a pair of signing details previously added via {@link #addOverrideSigningDetails} by
+ * the old signing details.
+ *
+ * @param oldSigningDetails the original signing detail of the package
+ * @throws SecurityException if the build is not debuggable
+ */
+ public static void removeOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails) {
+ synchronized (sOverrideSigningDetails) {
+ sOverrideSigningDetails.remove(oldSigningDetails);
+ }
+ }
+
+ /**
+ * Clear all pairs of signing details previously added via {@link #addOverrideSigningDetails}.
+ */
+ public static void clearOverrideSigningDetails() {
+ synchronized (sOverrideSigningDetails) {
+ sOverrideSigningDetails.clear();
+ }
}
/**
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index 66655fc..29c8350 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -16,6 +16,8 @@
package android.view;
+import static com.android.text.flags.Flags.handwritingCursorPosition;
+
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -557,7 +559,8 @@
}
private void requestFocusWithoutReveal(View view) {
- if (view instanceof EditText editText && !mState.mStylusDownWithinEditorBounds) {
+ if (!handwritingCursorPosition() && view instanceof EditText editText
+ && !mState.mStylusDownWithinEditorBounds) {
// If the stylus down point was inside the EditText's bounds, then the EditText will
// automatically set its cursor position nearest to the stylus down point when it
// gains focus. If the stylus down point was outside the EditText's bounds (within
@@ -576,6 +579,17 @@
} else {
view.requestFocus();
}
+ if (handwritingCursorPosition() && view instanceof EditText editText) {
+ // Move the cursor to the end of the paragraph closest to the stylus down point.
+ view.getLocationInWindow(mTempLocation);
+ int line = editText.getLineAtCoordinate(mState.mStylusDownY - mTempLocation[1]);
+ int paragraphEnd = TextUtils.indexOf(editText.getText(), '\n',
+ editText.getLayout().getLineStart(line));
+ if (paragraphEnd < 0) {
+ paragraphEnd = editText.getText().length();
+ }
+ editText.setSelection(paragraphEnd);
+ }
}
/**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 9fab1f7..a5ff48f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -7057,16 +7057,16 @@
/**
* Clears the request and callback previously set
- * through {@link View#setCredentialManagerRequest}.
+ * through {@link View#setPendingCredentialRequest}.
* Once this API is invoked, there will be no request fired to {@link CredentialManager}
* on future view focus events.
*
- * @see #setCredentialManagerRequest
+ * @see #setPendingCredentialRequest
*/
@FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
- public void clearCredentialManagerRequest() {
+ public void clearPendingCredentialRequest() {
if (Log.isLoggable(AUTOFILL_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_LOG_TAG, "clearCredentialManagerRequest called");
+ Log.v(AUTOFILL_LOG_TAG, "clearPendingCredentialRequest called");
}
mViewCredentialHandler = null;
}
@@ -7096,7 +7096,7 @@
* propagated for the given view
*/
@FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
- public void setCredentialManagerRequest(@NonNull GetCredentialRequest request,
+ public void setPendingCredentialRequest(@NonNull GetCredentialRequest request,
@NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
Preconditions.checkNotNull(request, "request must not be null");
Preconditions.checkNotNull(callback, "request must not be null");
@@ -9604,7 +9604,7 @@
structure.setIsCredential(isCredential());
}
if (getViewCredentialHandler() != null) {
- structure.setCredentialManagerRequest(
+ structure.setPendingCredentialRequest(
getViewCredentialHandler().getRequest(),
getViewCredentialHandler().getCallback());
}
@@ -10009,22 +10009,22 @@
* @hide
*/
public void onGetCredentialResponse(GetCredentialResponse response) {
- if (getCredentialManagerCallback() == null) {
+ if (getPendingCredentialCallback() == null) {
Log.w(AUTOFILL_LOG_TAG, "onGetCredentialResponse called but no callback found");
return;
}
- getCredentialManagerCallback().onResult(response);
+ getPendingCredentialCallback().onResult(response);
}
/**
* @hide
*/
public void onGetCredentialException(String errorType, String errorMsg) {
- if (getCredentialManagerCallback() == null) {
+ if (getPendingCredentialCallback() == null) {
Log.w(AUTOFILL_LOG_TAG, "onGetCredentialException called but no callback found");
return;
}
- getCredentialManagerCallback().onError(new GetCredentialException(errorType, errorMsg));
+ getPendingCredentialCallback().onError(new GetCredentialException(errorType, errorMsg));
}
/**
@@ -10055,13 +10055,13 @@
* the active {@link android.service.autofill.AutofillService} on
* the device.
*
- * <p>See {@link #setCredentialManagerRequest} for more info.
+ * <p>See {@link #setPendingCredentialRequest} for more info.
*
* @return The credential request associated with this View.
*/
@FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
@Nullable
- public final GetCredentialRequest getCredentialManagerRequest() {
+ public final GetCredentialRequest getPendingCredentialRequest() {
if (mViewCredentialHandler == null) {
return null;
}
@@ -10071,14 +10071,14 @@
/**
* Returns the callback that has previously been set up on this view through
- * the {@link #setCredentialManagerRequest} API.
+ * the {@link #setPendingCredentialRequest} API.
* If the return value is null, that means no callback, or request, has been set
* on the view and no {@link CredentialManager} flow will be invoked
* when this view is focused. Traditioanl autofill flows will still
* work, and autofillable content will still be returned through the
* {@link #autofill(AutofillValue)} )} API.
*
- * <p>See {@link #setCredentialManagerRequest} for more info.
+ * <p>See {@link #setPendingCredentialRequest} for more info.
*
* @return The callback associated with this view that will be invoked on a response from
* {@link CredentialManager} .
@@ -10086,7 +10086,7 @@
@FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
@Nullable
public final OutcomeReceiver<GetCredentialResponse,
- GetCredentialException> getCredentialManagerCallback() {
+ GetCredentialException> getPendingCredentialCallback() {
if (mViewCredentialHandler == null) {
return null;
}
@@ -11042,7 +11042,7 @@
AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId())));
}
if (getViewCredentialHandler() != null) {
- structure.setCredentialManagerRequest(
+ structure.setPendingCredentialRequest(
getViewCredentialHandler().getRequest(),
getViewCredentialHandler().getCallback());
}
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 1efd375..6c852c3 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -361,11 +361,11 @@
* {@link android.credentials.CredentialManager} request will be fired when this
* node is focused.
* <p> For details on how a request and callback can be set, see
- * {@link ViewStructure#setCredentialManagerRequest(GetCredentialRequest, OutcomeReceiver)}
+ * {@link ViewStructure#setPendingCredentialRequest(GetCredentialRequest, OutcomeReceiver)}
*/
@Nullable
@FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
- public GetCredentialRequest getCredentialManagerRequest() {
+ public GetCredentialRequest getPendingCredentialRequest() {
return null;
}
@@ -376,12 +376,12 @@
* {@link android.credentials.CredentialManager} request will be fired when this
* node is focused.
* <p> For details on how a request and callback can be set, see
- * {@link ViewStructure#setCredentialManagerRequest(GetCredentialRequest, OutcomeReceiver)}
+ * {@link ViewStructure#setPendingCredentialRequest(GetCredentialRequest, OutcomeReceiver)}
*/
@Nullable
@FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
public OutcomeReceiver<
- GetCredentialResponse, GetCredentialException> getCredentialManagerCallback() {
+ GetCredentialResponse, GetCredentialException> getPendingCredentialCallback() {
return null;
}
@@ -555,12 +555,12 @@
* @param callback the callback where the response or exception, is returned
*/
@FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
- public void setCredentialManagerRequest(@NonNull GetCredentialRequest request,
+ public void setPendingCredentialRequest(@NonNull GetCredentialRequest request,
@NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {}
/**
* Clears the credential request previously set through
- * {@link ViewStructure#setCredentialManagerRequest(GetCredentialRequest, OutcomeReceiver)}
+ * {@link ViewStructure#setPendingCredentialRequest(GetCredentialRequest, OutcomeReceiver)}
*/
@FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
public void clearCredentialManagerRequest() {}
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index 1acfc1b..644a7a9 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -233,6 +233,28 @@
public static final String DEVICE_CONFIG_INCLUDE_INVISIBLE_VIEW_GROUP_IN_ASSIST_STRUCTURE =
"include_invisible_view_group_in_assist_structure";
+ /**
+ * Bugfix flag, Autofill should ignore views resetting to empty states.
+ *
+ * See frameworks/base/services/autofill/bugfixes.aconfig#ignore_view_state_reset_to_empty
+ * for more information.
+ *
+ * @hide
+ */
+ public static final String DEVICE_CONFIG_IGNORE_VIEW_STATE_RESET_TO_EMPTY =
+ "ignore_view_state_reset_to_empty";
+
+ /**
+ * Bugfix flag, Autofill should ignore view updates if an Auth intent is showing.
+ *
+ * See frameworks/base/services/autofill/bugfixes.aconfig#relayout
+ * for more information.
+ *
+ * @hide
+ */
+ public static final String DEVICE_CONFIG_IGNORE_RELAYOUT_WHEN_AUTH_PENDING =
+ "ignore_relayout_auth_pending";
+
// END AUTOFILL FOR ALL APPS FLAGS //
@@ -494,6 +516,22 @@
false);
}
+ /** @hide */
+ public static boolean shouldIgnoreViewStateResetToEmpty() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_AUTOFILL,
+ DEVICE_CONFIG_IGNORE_VIEW_STATE_RESET_TO_EMPTY,
+ false);
+ }
+
+ /** @hide */
+ public static boolean shouldIgnoreRelayoutWhenAuthPending() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_AUTOFILL,
+ DEVICE_CONFIG_IGNORE_RELAYOUT_WHEN_AUTH_PENDING,
+ false);
+ }
+
/**
* Whether should enable multi-line filter
*
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 1484bfb..e15baae 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -974,7 +974,7 @@
mShouldIncludeInvisibleViewInAssistStructure =
AutofillFeatureFlags.shouldIncludeInvisibleViewInAssistStructure();
- mRelayoutFix = Flags.relayout();
+ mRelayoutFix = AutofillFeatureFlags.shouldIgnoreRelayoutWhenAuthPending();
mIsCredmanIntegrationEnabled = Flags.autofillCredmanIntegration();
}
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 14c5348..d12eda3 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1203,7 +1203,11 @@
* changes to this setting after that point.
*
* @param flag {@code true} if the WebView should use the database storage API
+ * @deprecated WebSQL is deprecated and this method will become a no-op on all
+ * Android versions once support is removed in Chromium. See
+ * https://developer.chrome.com/blog/deprecating-web-sql for more information.
*/
+ @Deprecated
public abstract void setDatabaseEnabled(boolean flag);
/**
@@ -1236,7 +1240,11 @@
*
* @return {@code true} if the database storage API is enabled
* @see #setDatabaseEnabled
+ * @deprecated WebSQL is deprecated and this method will become a no-op on all
+ * Android versions once support is removed in Chromium. See
+ * https://developer.chrome.com/blog/deprecating-web-sql for more information.
*/
+ @Deprecated
public abstract boolean getDatabaseEnabled();
/**
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 139ebc3..d40eeda 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2112,6 +2112,17 @@
}
}
+ boolean shouldDrawHighlightsOnTop = highContrastTextSmallTextRect()
+ && canvas.isHighContrastTextEnabled();
+
+ // If high contrast text is drawing background rectangles behind the text, those cover up
+ // the cursor and correction highlighter etc. So just draw the text first, then draw the
+ // others on top of the text. If high contrast text isn't enabled: draw text last, as usual.
+ if (shouldDrawHighlightsOnTop) {
+ drawLayout(canvas, layout, highlightPaths, highlightPaints, selectionHighlight,
+ selectionHighlightPaint, cursorOffsetVertical, shouldDrawHighlightsOnTop);
+ }
+
if (mCorrectionHighlighter != null) {
mCorrectionHighlighter.draw(canvas, cursorOffsetVertical);
}
@@ -2136,9 +2147,19 @@
mInsertModeController.onDraw(canvas);
}
+ if (!shouldDrawHighlightsOnTop) {
+ drawLayout(canvas, layout, highlightPaths, highlightPaints, selectionHighlight,
+ selectionHighlightPaint, cursorOffsetVertical, shouldDrawHighlightsOnTop);
+ }
+ }
+
+ private void drawLayout(Canvas canvas, Layout layout, List<Path> highlightPaths,
+ List<Paint> highlightPaints, Path selectionHighlight, Paint selectionHighlightPaint,
+ int cursorOffsetVertical, boolean shouldDrawHighlightsOnTop) {
if (mTextView.canHaveDisplayList() && canvas.isHardwareAccelerated()) {
drawHardwareAccelerated(canvas, layout, highlightPaths, highlightPaints,
- selectionHighlight, selectionHighlightPaint, cursorOffsetVertical);
+ selectionHighlight, selectionHighlightPaint, cursorOffsetVertical,
+ shouldDrawHighlightsOnTop);
} else {
layout.draw(canvas, highlightPaths, highlightPaints, selectionHighlight,
selectionHighlightPaint, cursorOffsetVertical);
@@ -2147,14 +2168,13 @@
private void drawHardwareAccelerated(Canvas canvas, Layout layout,
List<Path> highlightPaths, List<Paint> highlightPaints,
- Path selectionHighlight, Paint selectionHighlightPaint, int cursorOffsetVertical) {
+ Path selectionHighlight, Paint selectionHighlightPaint, int cursorOffsetVertical,
+ boolean shouldDrawHighlightsOnTop) {
final long lineRange = layout.getLineRangeForDraw(canvas);
int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
if (lastLine < 0) return;
- boolean shouldDrawHighlightsOnTop = highContrastTextSmallTextRect()
- && canvas.isHighContrastTextEnabled();
if (!shouldDrawHighlightsOnTop) {
layout.drawWithoutText(canvas, highlightPaths, highlightPaints, selectionHighlight,
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index a2d8d80..e3caf70 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -5127,7 +5127,10 @@
* @param viewId The id of the {@link AdapterView}
* @param intent The intent of the service which will be
* providing data to the RemoteViewsAdapter
+ * @deprecated use
+ * {@link #setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)} instead
*/
+ @Deprecated
public void setRemoteAdapter(@IdRes int viewId, Intent intent) {
if (remoteAdapterConversion()) {
addAction(new SetRemoteCollectionItemListAdapterAction(viewId, intent));
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 1721462..0373539 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -15490,8 +15490,9 @@
return x;
}
+ /** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- int getLineAtCoordinate(float y) {
+ public int getLineAtCoordinate(float y) {
y -= getTotalPaddingTop();
// Clamp the position to inside of the view.
y = Math.max(0.0f, y);
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java
index 7b8cdff..7e77f15 100644
--- a/core/java/android/window/TaskFragmentOperation.java
+++ b/core/java/android/window/TaskFragmentOperation.java
@@ -24,6 +24,7 @@
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.view.SurfaceControl;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -111,7 +112,8 @@
/**
* Creates a decor surface in the parent Task of the TaskFragment. The created decor surface
* will be provided in {@link TaskFragmentTransaction#TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED}
- * event callback.
+ * event callback. The decor surface can be used to draw the divider between TaskFragments or
+ * other decorations.
*/
public static final int OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE = 14;
@@ -135,6 +137,15 @@
*/
public static final int OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH = 17;
+ /**
+ * Sets whether the decor surface will be boosted. When not boosted, the decor surface is placed
+ * below any TaskFragments in untrusted mode or any activities with uid different from the
+ * TaskFragmentOrganizer uid and just above its owner TaskFragment; when boosted, the decor
+ * surface is placed above all the non-boosted windows in the Task, the content of these
+ * non-boosted windows will be hidden and inputs are disabled.
+ */
+ public static final int OP_TYPE_SET_DECOR_SURFACE_BOOSTED = 18;
+
@IntDef(prefix = { "OP_TYPE_" }, value = {
OP_TYPE_UNKNOWN,
OP_TYPE_CREATE_TASK_FRAGMENT,
@@ -155,6 +166,7 @@
OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE,
OP_TYPE_SET_DIM_ON_TASK,
OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH,
+ OP_TYPE_SET_DECOR_SURFACE_BOOSTED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface OperationType {}
@@ -186,12 +198,18 @@
private final boolean mMoveToBottomIfClearWhenLaunch;
+ private final boolean mBooleanValue;
+
+ @Nullable
+ private final SurfaceControl.Transaction mSurfaceTransaction;
+
private TaskFragmentOperation(@OperationType int opType,
@Nullable TaskFragmentCreationParams taskFragmentCreationParams,
@Nullable IBinder activityToken, @Nullable Intent activityIntent,
@Nullable Bundle bundle, @Nullable IBinder secondaryFragmentToken,
@Nullable TaskFragmentAnimationParams animationParams,
- boolean isolatedNav, boolean dimOnTask, boolean moveToBottomIfClearWhenLaunch) {
+ boolean isolatedNav, boolean dimOnTask, boolean moveToBottomIfClearWhenLaunch,
+ boolean booleanValue, @Nullable SurfaceControl.Transaction surfaceTransaction) {
mOpType = opType;
mTaskFragmentCreationParams = taskFragmentCreationParams;
mActivityToken = activityToken;
@@ -202,6 +220,8 @@
mIsolatedNav = isolatedNav;
mDimOnTask = dimOnTask;
mMoveToBottomIfClearWhenLaunch = moveToBottomIfClearWhenLaunch;
+ mBooleanValue = booleanValue;
+ mSurfaceTransaction = surfaceTransaction;
}
private TaskFragmentOperation(Parcel in) {
@@ -215,6 +235,8 @@
mIsolatedNav = in.readBoolean();
mDimOnTask = in.readBoolean();
mMoveToBottomIfClearWhenLaunch = in.readBoolean();
+ mBooleanValue = in.readBoolean();
+ mSurfaceTransaction = in.readTypedObject(SurfaceControl.Transaction.CREATOR);
}
@Override
@@ -229,6 +251,8 @@
dest.writeBoolean(mIsolatedNav);
dest.writeBoolean(mDimOnTask);
dest.writeBoolean(mMoveToBottomIfClearWhenLaunch);
+ dest.writeBoolean(mBooleanValue);
+ dest.writeTypedObject(mSurfaceTransaction, flags);
}
@NonNull
@@ -324,6 +348,22 @@
return mMoveToBottomIfClearWhenLaunch;
}
+ /** Returns the boolean value for this operation. */
+ public boolean getBooleanValue() {
+ return mBooleanValue;
+ }
+
+ /**
+ * Returns {@link SurfaceControl.Transaction} associated with this operation. Currently, this is
+ * only used by {@link TaskFragmentOperation#OP_TYPE_SET_DECOR_SURFACE_BOOSTED} to specify a
+ * {@link SurfaceControl.Transaction} that should be applied together with the transaction to
+ * change the decor surface layers.
+ */
+ @Nullable
+ public SurfaceControl.Transaction getSurfaceTransaction() {
+ return mSurfaceTransaction;
+ }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
@@ -349,6 +389,10 @@
sb.append(", isolatedNav=").append(mIsolatedNav);
sb.append(", dimOnTask=").append(mDimOnTask);
sb.append(", moveToBottomIfClearWhenLaunch=").append(mMoveToBottomIfClearWhenLaunch);
+ sb.append(", booleanValue=").append(mBooleanValue);
+ if (mSurfaceTransaction != null) {
+ sb.append(", surfaceTransaction=").append(mSurfaceTransaction);
+ }
sb.append('}');
return sb.toString();
@@ -358,7 +402,7 @@
public int hashCode() {
return Objects.hash(mOpType, mTaskFragmentCreationParams, mActivityToken, mActivityIntent,
mBundle, mSecondaryFragmentToken, mAnimationParams, mIsolatedNav, mDimOnTask,
- mMoveToBottomIfClearWhenLaunch);
+ mMoveToBottomIfClearWhenLaunch, mBooleanValue, mSurfaceTransaction);
}
@Override
@@ -376,7 +420,9 @@
&& Objects.equals(mAnimationParams, other.mAnimationParams)
&& mIsolatedNav == other.mIsolatedNav
&& mDimOnTask == other.mDimOnTask
- && mMoveToBottomIfClearWhenLaunch == other.mMoveToBottomIfClearWhenLaunch;
+ && mMoveToBottomIfClearWhenLaunch == other.mMoveToBottomIfClearWhenLaunch
+ && mBooleanValue == other.mBooleanValue
+ && Objects.equals(mSurfaceTransaction, other.mSurfaceTransaction);
}
@Override
@@ -414,6 +460,11 @@
private boolean mMoveToBottomIfClearWhenLaunch;
+ private boolean mBooleanValue;
+
+ @Nullable
+ private SurfaceControl.Transaction mSurfaceTransaction;
+
/**
* @param opType the {@link OperationType} of this {@link TaskFragmentOperation}.
*/
@@ -505,13 +556,37 @@
}
/**
+ * Sets the boolean value for this operation.
+ * TODO(b/327338038) migrate other boolean values to use shared mBooleanValue
+ */
+ @NonNull
+ public Builder setBooleanValue(boolean booleanValue) {
+ mBooleanValue = booleanValue;
+ return this;
+ }
+
+ /**
+ * Sets {@link SurfaceControl.Transaction} associated with this operation. Currently, this
+ * is only used by {@link TaskFragmentOperation#OP_TYPE_SET_DECOR_SURFACE_BOOSTED} to
+ * specify a {@link SurfaceControl.Transaction} that should be applied together with the
+ * transaction to change the decor surface layers.
+ */
+ @NonNull
+ public Builder setSurfaceTransaction(
+ @Nullable SurfaceControl.Transaction surfaceTransaction) {
+ mSurfaceTransaction = surfaceTransaction;
+ return this;
+ }
+
+ /**
* Constructs the {@link TaskFragmentOperation}.
*/
@NonNull
public TaskFragmentOperation build() {
return new TaskFragmentOperation(mOpType, mTaskFragmentCreationParams, mActivityToken,
mActivityIntent, mBundle, mSecondaryFragmentToken, mAnimationParams,
- mIsolatedNav, mDimOnTask, mMoveToBottomIfClearWhenLaunch);
+ mIsolatedNav, mDimOnTask, mMoveToBottomIfClearWhenLaunch, mBooleanValue,
+ mSurfaceTransaction);
}
}
}
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index ce74848..82e613e 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -77,4 +77,12 @@
description: "Properties to allow apps and activities to opt-in to cover display rendering"
bug: "312530526"
is_fixed_read_only: true
+}
+
+flag {
+ namespace: "windowing_sdk"
+ name: "enable_wm_extensions_for_all_flag"
+ description: "Whether to enable WM Extensions for all devices"
+ bug: "306666082"
+ is_fixed_read_only: true
}
\ No newline at end of file
diff --git a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
index 2c49303..2db3e65 100644
--- a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
+++ b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
@@ -16,6 +16,8 @@
package com.android.internal.accessibility.common;
+import android.os.SystemProperties;
+
/**
* Collection of common constants for accessibility magnification.
*/
@@ -31,6 +33,7 @@
/** Minimum supported value for magnification scale. */
public static final float SCALE_MIN_VALUE = 1.0f;
- /** Maximum supported value for magnification scale. */
- public static final float SCALE_MAX_VALUE = 8.0f;
+ /** Maximum supported value for magnification scale. Default of 8.0. */
+ public static final float SCALE_MAX_VALUE =
+ Float.parseFloat(SystemProperties.get("ro.config.max_magnification_scale", "8.0"));
}
diff --git a/core/java/com/android/internal/compat/Android.bp b/core/java/com/android/internal/compat/Android.bp
new file mode 100644
index 0000000..9ff05a6
--- /dev/null
+++ b/core/java/com/android/internal/compat/Android.bp
@@ -0,0 +1,7 @@
+aconfig_declarations {
+ name: "compat_logging_flags",
+ package: "com.android.internal.compat.flags",
+ srcs: [
+ "compat_logging_flags.aconfig",
+ ],
+}
diff --git a/core/java/com/android/internal/compat/ChangeReporter.java b/core/java/com/android/internal/compat/ChangeReporter.java
index b9d3df6..6ff546f 100644
--- a/core/java/com/android/internal/compat/ChangeReporter.java
+++ b/core/java/com/android/internal/compat/ChangeReporter.java
@@ -24,6 +24,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.compat.flags.Flags;
import com.android.internal.util.FrameworkStatsLog;
import java.lang.annotation.Retention;
@@ -40,7 +41,7 @@
* @hide
*/
public final class ChangeReporter {
- private static final String TAG = "CompatibilityChangeReporter";
+ private static final String TAG = "CompatChangeReporter";
private int mSource;
private static final class ChangeReport {
@@ -84,19 +85,34 @@
* Report the change to stats log and to the debug log if the change was not previously
* logged already.
*
+ * @param uid affected by the change
+ * @param changeId the reported change id
+ * @param state of the reported change - enabled/disabled/only logged
+ * @param isLoggableBySdk whether debug logging is allowed for this change based on target
+ * SDK version. This is combined with other logic to determine whether to
+ * actually log. If the sdk version does not matter, should be true.
+ */
+ public void reportChange(int uid, long changeId, int state, boolean isLoggableBySdk) {
+ if (shouldWriteToStatsLog(uid, changeId, state)) {
+ FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid,
+ changeId, state, mSource);
+ }
+ if (shouldWriteToDebug(uid, changeId, state, isLoggableBySdk)) {
+ debugLog(uid, changeId, state);
+ }
+ markAsReported(uid, new ChangeReport(changeId, state));
+ }
+
+ /**
+ * Report the change to stats log and to the debug log if the change was not previously
+ * logged already.
+ *
* @param uid affected by the change
* @param changeId the reported change id
* @param state of the reported change - enabled/disabled/only logged
*/
public void reportChange(int uid, long changeId, int state) {
- if (shouldWriteToStatsLog(uid, changeId, state)) {
- FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid,
- changeId, state, mSource);
- }
- if (shouldWriteToDebug(uid, changeId, state)) {
- debugLog(uid, changeId, state);
- }
- markAsReported(uid, new ChangeReport(changeId, state));
+ reportChange(uid, changeId, state, true);
}
/**
@@ -130,14 +146,43 @@
/**
* Returns whether the next report should be logged to logcat.
*
- * @param uid affected by the change
- * @param changeId the reported change id
- * @param state of the reported change - enabled/disabled/only logged
+ * @param uid affected by the change
+ * @param changeId the reported change id
+ * @param state of the reported change - enabled/disabled/only logged
+ * @param isLoggableBySdk whether debug logging is allowed for this change based on target
+ * SDK version. This is combined with other logic to determine whether to
+ * actually log. If the sdk version does not matter, should be true.
+ * @return true if the report should be logged
+ */
+ @VisibleForTesting
+ public boolean shouldWriteToDebug(
+ int uid, long changeId, int state, boolean isLoggableBySdk) {
+ // If log all bit is on, always return true.
+ if (mDebugLogAll) return true;
+ // If the change has already been reported, do not write.
+ if (isAlreadyReported(uid, new ChangeReport(changeId, state))) return false;
+
+ // If the flag is turned off or the TAG's logging is forced to debug level with
+ // `adb setprop log.tag.CompatChangeReporter=DEBUG`, write to debug since the above checks
+ // have already passed.
+ boolean skipLoggingFlag = Flags.skipOldAndDisabledCompatLogging();
+ if (!skipLoggingFlag || Log.isLoggable(TAG, Log.DEBUG)) return true;
+
+ // Log if the change is enabled and targets the latest sdk version.
+ return isLoggableBySdk && state != STATE_DISABLED;
+ }
+
+ /**
+ * Returns whether the next report should be logged to logcat.
+ *
+ * @param uid affected by the change
+ * @param changeId the reported change id
+ * @param state of the reported change - enabled/disabled/only logged
* @return true if the report should be logged
*/
@VisibleForTesting
public boolean shouldWriteToDebug(int uid, long changeId, int state) {
- return mDebugLogAll || !isAlreadyReported(uid, new ChangeReport(changeId, state));
+ return shouldWriteToDebug(uid, changeId, state, true);
}
private boolean isAlreadyReported(int uid, ChangeReport report) {
diff --git a/core/java/com/android/internal/compat/compat_logging_flags.aconfig b/core/java/com/android/internal/compat/compat_logging_flags.aconfig
new file mode 100644
index 0000000..fab3856
--- /dev/null
+++ b/core/java/com/android/internal/compat/compat_logging_flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.internal.compat.flags"
+
+flag {
+ name: "skip_old_and_disabled_compat_logging"
+ namespace: "platform_compat"
+ description: "Feature flag for skipping debug logging for changes that do not target the latest sdk or are disabled"
+ bug: "323949942"
+ is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index cbe0700..d4dcec9 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -93,6 +93,9 @@
throw ex;
}
+ if (peer.getUid() != Process.SYSTEM_UID) {
+ throw new ZygoteSecurityException("Only system UID is allowed to connect to Zygote.");
+ }
isEof = false;
}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index dc3b5a8..0257033 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -33,8 +33,10 @@
import com.android.internal.inputmethod.InputBindResult;
/**
- * Public interface to the global input method manager, used by all client
- * applications.
+ * Public interface to the global input method manager, used by all client applications.
+ *
+ * When adding new methods, make sure the associated user can be inferred from the arguments.
+ * Consider passing the associated userId when not already passing a display id or a window token.
*/
interface IInputMethodManager {
void addClient(in IInputMethodClient client, in IRemoteInputConnection inputmethod,
diff --git a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
index 54c4cd5..e0cc055 100644
--- a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
+++ b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
@@ -354,6 +354,18 @@
return result;
}
+static uid_t getSocketPeerUid(int socket, const std::function<void(const std::string&)>& fail_fn) {
+ struct ucred credentials;
+ socklen_t cred_size = sizeof credentials;
+ if (getsockopt(socket, SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1
+ || cred_size != sizeof credentials) {
+ fail_fn(CREATE_ERROR("Failed to get socket credentials, %s",
+ strerror(errno)));
+ }
+
+ return credentials.uid;
+}
+
// Read all lines from the current command into the buffer, and then reset the buffer, so
// we will start reading again at the beginning of the command, starting with the argument
// count. And we don't need access to the fd to do so.
@@ -413,19 +425,12 @@
fail_fn_z("Failed to retrieve session socket timeout");
}
- struct ucred credentials;
- socklen_t cred_size = sizeof credentials;
- if (getsockopt(n_buffer->getFd(), SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1
- || cred_size != sizeof credentials) {
- fail_fn_1(CREATE_ERROR("ForkRepeatedly failed to get initial credentials, %s",
- strerror(errno)));
+ uid_t peerUid = getSocketPeerUid(session_socket, fail_fn_1);
+ if (peerUid != static_cast<uid_t>(expected_uid)) {
+ return JNI_FALSE;
}
-
bool first_time = true;
do {
- if (credentials.uid != static_cast<uid_t>(expected_uid)) {
- return JNI_FALSE;
- }
n_buffer->readAllLines(first_time ? fail_fn_1 : fail_fn_n);
n_buffer->reset();
int pid = zygote::forkApp(env, /* no pipe FDs */ -1, -1, session_socket_fds,
@@ -453,6 +458,7 @@
}
}
for (;;) {
+ bool valid_session_socket = true;
// Clear buffer and get count from next command.
n_buffer->clear();
// Poll isn't strictly necessary for now. But without it, disconnect is hard to detect.
@@ -463,25 +469,50 @@
if ((fd_structs[SESSION_IDX].revents & POLLIN) != 0) {
if (n_buffer->getCount(fail_fn_z) != 0) {
break;
- } // else disconnected;
+ } else {
+ // Session socket was disconnected
+ valid_session_socket = false;
+ close(session_socket);
+ }
} else if (poll_res == 0 || (fd_structs[ZYGOTE_IDX].revents & POLLIN) == 0) {
fail_fn_z(
CREATE_ERROR("Poll returned with no descriptors ready! Poll returned %d", poll_res));
}
- // We've now seen either a disconnect or connect request.
- close(session_socket);
- int new_fd = TEMP_FAILURE_RETRY(accept(zygote_socket_fd, nullptr, nullptr));
+ int new_fd = -1;
+ do {
+ // We've now seen either a disconnect or connect request.
+ new_fd = TEMP_FAILURE_RETRY(accept(zygote_socket_fd, nullptr, nullptr));
+ if (new_fd == -1) {
+ fail_fn_z(CREATE_ERROR("Accept(%d) failed: %s", zygote_socket_fd, strerror(errno)));
+ }
+ uid_t newPeerUid = getSocketPeerUid(new_fd, fail_fn_1);
+ if (newPeerUid != static_cast<uid_t>(expected_uid)) {
+ ALOGW("Dropping new connection with a mismatched uid %d\n", newPeerUid);
+ close(new_fd);
+ new_fd = -1;
+ } else {
+ // If we still have a valid session socket, close it now
+ if (valid_session_socket) {
+ close(session_socket);
+ }
+ valid_session_socket = true;
+ }
+ } while (!valid_session_socket);
+
+ // At this point we either have a valid new connection (new_fd > 0), or
+ // an existing session socket we can poll on
if (new_fd == -1) {
- fail_fn_z(CREATE_ERROR("Accept(%d) failed: %s", zygote_socket_fd, strerror(errno)));
+ // The new connection wasn't valid, and we still have an old one; retry polling
+ continue;
}
if (new_fd != session_socket) {
- // Move new_fd back to the old value, so that we don't have to change Java-level data
- // structures to reflect a change. This implicitly closes the old one.
- if (TEMP_FAILURE_RETRY(dup2(new_fd, session_socket)) != session_socket) {
- fail_fn_z(CREATE_ERROR("Failed to move fd %d to %d: %s",
- new_fd, session_socket, strerror(errno)));
- }
- close(new_fd); // On Linux, fd is closed even if EINTR is returned.
+ // Move new_fd back to the old value, so that we don't have to change Java-level data
+ // structures to reflect a change. This implicitly closes the old one.
+ if (TEMP_FAILURE_RETRY(dup2(new_fd, session_socket)) != session_socket) {
+ fail_fn_z(CREATE_ERROR("Failed to move fd %d to %d: %s",
+ new_fd, session_socket, strerror(errno)));
+ }
+ close(new_fd); // On Linux, fd is closed even if EINTR is returned.
}
// If we ever return, we effectively reuse the old Java ZygoteConnection.
// None of its state needs to change.
@@ -493,13 +524,6 @@
fail_fn_z(CREATE_ERROR("Failed to set send timeout for socket %d: %s",
session_socket, strerror(errno)));
}
- if (getsockopt(session_socket, SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1) {
- fail_fn_z(CREATE_ERROR("ForkMany failed to get credentials: %s", strerror(errno)));
- }
- if (cred_size != sizeof credentials) {
- fail_fn_z(CREATE_ERROR("ForkMany credential size = %d, should be %d",
- cred_size, static_cast<int>(sizeof credentials)));
- }
}
first_time = false;
} while (n_buffer->isSimpleForkCommand(minUid, fail_fn_n));
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1acdc75..8ea742d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6916,6 +6916,12 @@
<permission android:name="android.permission.MANAGE_MEDIA_PROJECTION"
android:protectionLevel="signature" />
+ <!-- @hide @TestApi Allows an application to record sensitive content during media
+ projection. This is intended for on device screen recording system app.
+ @FlaggedApi("android.permission.flags.sensitive_notification_app_protection") -->
+ <permission android:name="android.permission.RECORD_SENSITIVE_CONTENT"
+ android:protectionLevel="signature"/>
+
<!-- @SystemApi Allows an application to read install sessions
@hide This is not a third-party API (intended for system apps). -->
<permission android:name="android.permission.READ_INSTALL_SESSIONS"
diff --git a/core/res/res/values-watch/themes_device_defaults.xml b/core/res/res/values-watch/themes_device_defaults.xml
index 6e804c0..9ad577a 100644
--- a/core/res/res/values-watch/themes_device_defaults.xml
+++ b/core/res/res/values-watch/themes_device_defaults.xml
@@ -181,19 +181,95 @@
<style name="Theme.DeviceDefault.Dialog.Alert" parent="Theme.Material.Dialog.Alert">
<item name="android:windowFullscreen">true</item>
- <!-- Color palette Dark -->
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+ <!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
- <item name="colorForeground">@color/foreground_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
+ <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_dark_device_default</item>
+ <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_dark_device_default</item>
+ <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_dark_device_default</item>
+ <item name="colorSurface">@color/surface_dark</item>
+ <item name="colorSurfaceHighlight">@color/surface_highlight_dark</item>
+ <item name="colorSurfaceVariant">@color/surface_variant_dark</item>
+ <item name="colorSurfaceHeader">@color/surface_header_dark</item>
+ <item name="colorError">@color/error_color_device_default_dark</item>
<item name="colorBackground">@color/background_device_default_dark</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
- <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_device_default</item>
- <item name="colorButtonNormal">@color/button_normal_device_default_dark</item>
- <item name="colorError">@color/error_color_device_default_dark</item>
- <item name="disabledAlpha">@dimen/disabled_alpha_device_default</item>
- <item name="primaryContentAlpha">@dimen/primary_content_alpha_device_default</item>
- <item name="secondaryContentAlpha">@dimen/secondary_content_alpha_device_default</item>
+ <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
+ <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
+ <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
+ <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item>
+ <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item>
+ <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item>
+ <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+ <item name="colorForeground">@color/foreground_device_default_dark</item>
+ <item name="colorForegroundInverse">@color/foreground_device_default_light</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
+
+ <!-- Progress bar attributes -->
+ <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+ <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
+
+ <!-- Toolbar attributes -->
+ <item name="toolbarStyle">@style/Widget.DeviceDefault.Toolbar</item>
+
+ <item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item>
+ <item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item>
+ <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_dark</item>
+ <item name="materialColorOnPrimaryFixedVariant">@color/system_on_primary_fixed_variant</item>
+ <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_dark</item>
+ <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_dark</item>
+ <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_dark</item>
+ <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_dark</item>
+ <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
+ <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item>
+ <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
+ <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+ <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
+ <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
+ <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
+ <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item>
+ <item name="materialColorErrorContainer">@color/system_error_container_dark</item>
+ <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
+ <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+ <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
+ <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
+ <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item>
+ <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item>
+ <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
+ <item name="materialColorPrimaryContainer">@color/system_primary_container_dark</item>
+ <item name="materialColorOnBackground">@color/system_on_background_dark</item>
+ <item name="materialColorPrimaryFixed">@color/system_primary_fixed</item>
+ <item name="materialColorOnSecondary">@color/system_on_secondary_dark</item>
+ <item name="materialColorOnTertiary">@color/system_on_tertiary_dark</item>
+ <item name="materialColorSurfaceDim">@color/system_surface_dim_dark</item>
+ <item name="materialColorSurfaceBright">@color/system_surface_bright_dark</item>
+ <item name="materialColorOnError">@color/system_on_error_dark</item>
+ <item name="materialColorSurface">@color/system_surface_dark</item>
+ <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_dark</item>
+ <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
+ <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
+ <item name="materialColorOutline">@color/system_outline_dark</item>
+ <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
+ <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
+ <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
+ <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
+ <item name="materialColorPrimary">@color/system_primary_dark</item>
+ <item name="materialColorSecondary">@color/system_secondary_dark</item>
+ <item name="materialColorTertiary">@color/system_tertiary_dark</item>
+ <item name="materialColorError">@color/system_error_dark</item>
</style>
<!-- DeviceDefault theme for a window that should look like the Settings app. -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index d89f236..5e900f7 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -3287,10 +3287,9 @@
-->
<attr name="enableOnBackInvokedCallback" format="boolean"/>
- <!-- Specifies permissions necessary to launch this activity via
- {@link android.content.Context#startActivity} when passing content URIs. The default
- value is {@code none}, meaning no specific permissions are required. Setting this
- attribute restricts activity invocation based on the invoker's permissions. If the
+ <!-- Specifies permissions necessary to launch this activity when passing content URIs. The
+ default value is {@code none}, meaning no specific permissions are required. Setting
+ this attribute restricts activity invocation based on the invoker's permissions. If the
invoker doesn't have the required permissions, the activity start will be denied via a
{@link java.lang.SecurityException}.
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
index a034653..10ac05d 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
@@ -88,13 +88,6 @@
}
@Test
- public void setInternalHalCallback_callbackSetInHal() throws Exception {
- mRadioModule.setInternalHalCallback();
-
- verify(mBroadcastRadioMock).setTunerCallback(any());
- }
-
- @Test
public void getImage_withValidIdFromRadioModule() {
int imageId = 1;
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
index 262f167..755bcdb 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
@@ -192,66 +192,6 @@
mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0];
return null;
}).when(mBroadcastRadioMock).setTunerCallback(any());
- mRadioModule.setInternalHalCallback();
-
- doAnswer(invocation -> {
- android.hardware.broadcastradio.ProgramSelector halSel =
- (android.hardware.broadcastradio.ProgramSelector) invocation.getArguments()[0];
- mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(halSel, SIGNAL_QUALITY);
- if (halSel.primaryId.type != IdentifierType.AMFM_FREQUENCY_KHZ) {
- throw new ServiceSpecificException(Result.NOT_SUPPORTED);
- }
- mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
- return Result.OK;
- }).when(mBroadcastRadioMock).tune(any());
-
- doAnswer(invocation -> {
- if ((boolean) invocation.getArguments()[0]) {
- mHalCurrentInfo.selector.primaryId.value += AM_FM_FREQUENCY_SPACING;
- } else {
- mHalCurrentInfo.selector.primaryId.value -= AM_FM_FREQUENCY_SPACING;
- }
- mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
- mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
- mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
- return Result.OK;
- }).when(mBroadcastRadioMock).step(anyBoolean());
-
- doAnswer(invocation -> {
- if (mHalCurrentInfo == null) {
- android.hardware.broadcastradio.ProgramSelector placeHolderSelector =
- AidlTestUtils.makeHalFmSelector(/* freq= */ 97300);
-
- mHalTunerCallback.onTuneFailed(Result.TIMEOUT, placeHolderSelector);
- return Result.OK;
- }
- mHalCurrentInfo.selector.primaryId.value = getSeekFrequency(
- mHalCurrentInfo.selector.primaryId.value,
- !(boolean) invocation.getArguments()[0]);
- mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
- mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
- mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
- return Result.OK;
- }).when(mBroadcastRadioMock).seek(anyBoolean(), anyBoolean());
-
- doReturn(null).when(mBroadcastRadioMock).getImage(anyInt());
-
- doAnswer(invocation -> {
- int configFlag = (int) invocation.getArguments()[0];
- if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
- throw new ServiceSpecificException(Result.NOT_SUPPORTED);
- }
- return mHalConfigMap.getOrDefault(configFlag, false);
- }).when(mBroadcastRadioMock).isConfigFlagSet(anyInt());
-
- doAnswer(invocation -> {
- int configFlag = (int) invocation.getArguments()[0];
- if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
- throw new ServiceSpecificException(Result.NOT_SUPPORTED);
- }
- mHalConfigMap.put(configFlag, (boolean) invocation.getArguments()[1]);
- return null;
- }).when(mBroadcastRadioMock).setConfigFlag(anyInt(), anyBoolean());
}
@After
@@ -330,6 +270,7 @@
expect.withMessage("Close state of broadcast radio service session")
.that(mTunerSessions[0].isClosed()).isTrue();
+ verify(mBroadcastRadioMock).unsetTunerCallback();
}
@Test
@@ -351,6 +292,7 @@
.that(mTunerSessions[index].isClosed()).isFalse();
}
}
+ verify(mBroadcastRadioMock, never()).unsetTunerCallback();
}
@Test
@@ -378,6 +320,7 @@
expect.withMessage("Close state of broadcast radio service session of index %s", index)
.that(mTunerSessions[index].isClosed()).isTrue();
}
+ verify(mBroadcastRadioMock).unsetTunerCallback();
}
@Test
@@ -1295,6 +1238,71 @@
mAidlTunerCallbackMocks[index] = mock(android.hardware.radio.ITunerCallback.class);
mTunerSessions[index] = mRadioModule.openSession(mAidlTunerCallbackMocks[index]);
}
+ setupMockedHalTunerSession();
+ }
+
+ private void setupMockedHalTunerSession() throws Exception {
+ expect.withMessage("Registered HAL tuner callback").that(mHalTunerCallback)
+ .isNotNull();
+
+ doAnswer(invocation -> {
+ android.hardware.broadcastradio.ProgramSelector halSel =
+ (android.hardware.broadcastradio.ProgramSelector) invocation.getArguments()[0];
+ mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(halSel, SIGNAL_QUALITY);
+ if (halSel.primaryId.type != IdentifierType.AMFM_FREQUENCY_KHZ) {
+ throw new ServiceSpecificException(Result.NOT_SUPPORTED);
+ }
+ mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+ return Result.OK;
+ }).when(mBroadcastRadioMock).tune(any());
+
+ doAnswer(invocation -> {
+ if ((boolean) invocation.getArguments()[0]) {
+ mHalCurrentInfo.selector.primaryId.value += AM_FM_FREQUENCY_SPACING;
+ } else {
+ mHalCurrentInfo.selector.primaryId.value -= AM_FM_FREQUENCY_SPACING;
+ }
+ mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+ mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+ mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+ return Result.OK;
+ }).when(mBroadcastRadioMock).step(anyBoolean());
+
+ doAnswer(invocation -> {
+ if (mHalCurrentInfo == null) {
+ android.hardware.broadcastradio.ProgramSelector placeHolderSelector =
+ AidlTestUtils.makeHalFmSelector(/* freq= */ 97300);
+
+ mHalTunerCallback.onTuneFailed(Result.TIMEOUT, placeHolderSelector);
+ return Result.OK;
+ }
+ mHalCurrentInfo.selector.primaryId.value = getSeekFrequency(
+ mHalCurrentInfo.selector.primaryId.value,
+ !(boolean) invocation.getArguments()[0]);
+ mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+ mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+ mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+ return Result.OK;
+ }).when(mBroadcastRadioMock).seek(anyBoolean(), anyBoolean());
+
+ doReturn(null).when(mBroadcastRadioMock).getImage(anyInt());
+
+ doAnswer(invocation -> {
+ int configFlag = (int) invocation.getArguments()[0];
+ if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
+ throw new ServiceSpecificException(Result.NOT_SUPPORTED);
+ }
+ return mHalConfigMap.getOrDefault(configFlag, false);
+ }).when(mBroadcastRadioMock).isConfigFlagSet(anyInt());
+
+ doAnswer(invocation -> {
+ int configFlag = (int) invocation.getArguments()[0];
+ if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
+ throw new ServiceSpecificException(Result.NOT_SUPPORTED);
+ }
+ mHalConfigMap.put(configFlag, (boolean) invocation.getArguments()[1]);
+ return null;
+ }).when(mBroadcastRadioMock).setConfigFlag(anyInt(), anyBoolean());
}
private long getSeekFrequency(long currentFrequency, boolean seekDown) {
diff --git a/core/tests/PlatformCompatFramework/Android.bp b/core/tests/PlatformCompatFramework/Android.bp
index 95e23ad..2621d28 100644
--- a/core/tests/PlatformCompatFramework/Android.bp
+++ b/core/tests/PlatformCompatFramework/Android.bp
@@ -18,6 +18,7 @@
static_libs: [
"junit",
"androidx.test.rules",
+ "flag-junit",
],
platform_apis: true,
}
diff --git a/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java b/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java
index a052543..12a42f9 100644
--- a/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java
+++ b/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java
@@ -19,9 +19,17 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import com.android.internal.compat.flags.Flags;
+
+import org.junit.Rule;
import org.junit.Test;
public class ChangeReporterTest {
+
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Test
public void testStatsLogOnce() {
ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
@@ -63,7 +71,7 @@
ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
int myUid = 1022, otherUid = 1023;
long myChangeId = 500L, otherChangeId = 600L;
- int myState = ChangeReporter.STATE_ENABLED, otherState = ChangeReporter.STATE_DISABLED;
+ int myState = ChangeReporter.STATE_ENABLED, otherState = ChangeReporter.STATE_LOGGED;
assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myState));
reporter.reportChange(myUid, myChangeId, myState);
@@ -112,4 +120,80 @@
reporter.stopDebugLogAll();
assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myState));
}
+
+ @Test
+ public void testDebugLogWithFlagOnAndOldSdk() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_SKIP_OLD_AND_DISABLED_COMPAT_LOGGING);
+ ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
+ int myUid = 1022;
+ long myChangeId = 500L;
+ int myEnabledState = ChangeReporter.STATE_ENABLED;
+ int myDisabledState = ChangeReporter.STATE_DISABLED;
+
+ // Report will not log if target sdk is before the previous version.
+ assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, false));
+
+ reporter.resetReportedChanges(myUid);
+
+ // Report will be logged if target sdk is the latest version.
+ assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, true));
+
+ reporter.resetReportedChanges(myUid);
+
+ // If the report is disabled, the sdk version shouldn't matter.
+ assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myDisabledState, true));
+ }
+
+ @Test
+ public void testDebugLogWithFlagOnAndDisabledChange() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_SKIP_OLD_AND_DISABLED_COMPAT_LOGGING);
+ ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
+ int myUid = 1022;
+ long myChangeId = 500L;
+ int myEnabledState = ChangeReporter.STATE_ENABLED;
+ int myDisabledState = ChangeReporter.STATE_DISABLED;
+
+ // Report will not log if the change is disabled.
+ assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myDisabledState, true));
+
+ reporter.resetReportedChanges(myUid);
+
+ // Report will be logged if the change is enabled.
+ assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, true));
+
+ reporter.resetReportedChanges(myUid);
+
+ // If the report is not the latest version, the disabled state doesn't matter.
+ assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, false));
+ }
+
+ @Test
+ public void testDebugLogWithFlagOff() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_SKIP_OLD_AND_DISABLED_COMPAT_LOGGING);
+ ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
+ int myUid = 1022;
+ long myChangeId = 500L;
+ int myEnabledState = ChangeReporter.STATE_ENABLED;
+ int myDisabledState = ChangeReporter.STATE_DISABLED;
+
+ // Report will be logged even if the change is not the latest sdk but the flag is off.
+ assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, false));
+
+ reporter.resetReportedChanges(myUid);
+
+ // Report will be logged if the change is enabled and the latest sdk but the flag is off.
+ assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, true));
+
+ reporter.resetReportedChanges(myUid);
+
+ // Report will be logged if the change is disabled and the latest sdk but the flag is
+ // off.
+ assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myDisabledState, true));
+
+ reporter.resetReportedChanges(myUid);
+
+ // Report will be logged if the change is disabled and not the latest sdk but the flag is
+ // off.
+ assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myDisabledState, false));
+ }
}
diff --git a/core/tests/coretests/src/android/app/assist/AssistStructureTest.java b/core/tests/coretests/src/android/app/assist/AssistStructureTest.java
index abeb08c..1f2788c 100644
--- a/core/tests/coretests/src/android/app/assist/AssistStructureTest.java
+++ b/core/tests/coretests/src/android/app/assist/AssistStructureTest.java
@@ -266,8 +266,8 @@
assertThat(view.getViewRootImpl()).isNotNull();
ViewNodeBuilder viewStructure = new ViewNodeBuilder();
viewStructure.setAutofillId(view.getAutofillId());
- viewStructure.setCredentialManagerRequest(view.getCredentialManagerRequest(),
- view.getCredentialManagerCallback());
+ viewStructure.setPendingCredentialRequest(view.getPendingCredentialRequest(),
+ view.getPendingCredentialCallback());
view.onProvideAutofillStructure(viewStructure, /* flags= */ 0);
ViewNodeParcelable viewNodeParcelable = new ViewNodeParcelable(viewStructure.getViewNode());
@@ -289,17 +289,20 @@
assertThat(view.getViewRootImpl()).isNotNull();
ViewNodeBuilder viewStructure = new ViewNodeBuilder();
- viewStructure.setCredentialManagerRequest(view.getCredentialManagerRequest(),
- view.getCredentialManagerCallback());
+ if (view.getPendingCredentialRequest() != null
+ && view.getPendingCredentialCallback() != null) {
+ viewStructure.setPendingCredentialRequest(view.getPendingCredentialRequest(),
+ view.getPendingCredentialCallback());
+ }
- assertEquals(viewStructure.getCredentialManagerRequest(), GET_CREDENTIAL_REQUEST);
- assertEquals(viewStructure.getCredentialManagerCallback(),
+ assertEquals(viewStructure.getPendingCredentialRequest(), GET_CREDENTIAL_REQUEST);
+ assertEquals(viewStructure.getPendingCredentialCallback(),
GET_CREDENTIAL_REQUEST_CALLBACK);
viewStructure.clearCredentialManagerRequest();
- assertNull(viewStructure.getCredentialManagerRequest());
- assertNull(viewStructure.getCredentialManagerCallback());
+ assertNull(viewStructure.getPendingCredentialRequest());
+ assertNull(viewStructure.getPendingCredentialCallback());
}
@Test
@@ -386,14 +389,14 @@
EditText view = new EditText(mContext);
view.setText("Big Hint in Little View");
view.setAutofillHints(BIG_STRING);
- view.setCredentialManagerRequest(GET_CREDENTIAL_REQUEST, GET_CREDENTIAL_REQUEST_CALLBACK);
+ view.setPendingCredentialRequest(GET_CREDENTIAL_REQUEST, GET_CREDENTIAL_REQUEST_CALLBACK);
return view;
}
private EditText newCredentialView() {
EditText view = new EditText(mContext);
view.setText("Credential Request");
- view.setCredentialManagerRequest(GET_CREDENTIAL_REQUEST, GET_CREDENTIAL_REQUEST_CALLBACK);
+ view.setPendingCredentialRequest(GET_CREDENTIAL_REQUEST, GET_CREDENTIAL_REQUEST_CALLBACK);
return view;
}
@@ -421,8 +424,8 @@
assertThat(view.getAutofillId()).isNotNull();
assertThat(view.getText().toString()).isEqualTo("Big Hint in Little View");
- assertThat(view.getCredentialManagerRequest()).isEqualTo(GET_CREDENTIAL_REQUEST);
- assertThat(view.getCredentialManagerCallback()).isEqualTo(GET_CREDENTIAL_REQUEST_CALLBACK);
+ assertThat(view.getPendingCredentialRequest()).isEqualTo(GET_CREDENTIAL_REQUEST);
+ assertThat(view.getPendingCredentialCallback()).isEqualTo(GET_CREDENTIAL_REQUEST_CALLBACK);
}
/**
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index b60b806f..a5c9624 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -23,6 +23,8 @@
import static android.view.inputmethod.Flags.initiationWithoutInputConnection;
import static android.view.stylus.HandwritingTestUtil.createView;
+import static com.android.text.flags.Flags.handwritingCursorPosition;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assume.assumeFalse;
@@ -129,6 +131,7 @@
public void onTouchEvent_startHandwriting_when_stylusMoveOnce_withinHWArea() {
mTestView1.setText("hello");
when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4);
+ when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(0);
mHandwritingInitiator.onInputConnectionCreated(mTestView1);
final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
@@ -148,9 +151,51 @@
// After IMM.startHandwriting is triggered, onTouchEvent should return true for ACTION_MOVE
// events so that the events are not dispatched to the view tree.
assertThat(onTouchEventResult2).isTrue();
- // Since the stylus down point was inside the TextView's bounds, the handwriting initiator
- // does not need to set the cursor position.
- verify(mTestView1, never()).setSelection(anyInt());
+ if (handwritingCursorPosition()) {
+ // Cursor is placed at the end of the text.
+ verify(mTestView1).setSelection(5);
+ } else {
+ // Since the stylus down point was inside the TextView's bounds, the handwriting
+ // initiator does not need to set the cursor position.
+ verify(mTestView1, never()).setSelection(anyInt());
+ }
+ }
+
+ @Test
+ public void onTouchEvent_startHandwriting_multipleParagraphs() {
+ // End of line 0 is offset 10, end of line 1 is offset 20, end of line 2 is offset 30, end
+ // of line 3 is offset 40.
+ mTestView1.setText("line 0 \nline 1 \nline 2 \nline 3 ");
+ mTestView1.layout(0, 0, 500, 500);
+ when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4);
+ when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(2);
+
+ mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+ final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
+ final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2;
+ MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
+ boolean onTouchEventResult1 = mHandwritingInitiator.onTouchEvent(stylusEvent1);
+
+ final int x2 = x1 + mHandwritingSlop * 2;
+ final int y2 = y1;
+
+ MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
+ boolean onTouchEventResult2 = mHandwritingInitiator.onTouchEvent(stylusEvent2);
+
+ // Stylus movement within HandwritingArea should trigger IMM.startHandwriting once.
+ verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1);
+ assertThat(onTouchEventResult1).isFalse();
+ // After IMM.startHandwriting is triggered, onTouchEvent should return true for ACTION_MOVE
+ // events so that the events are not dispatched to the view tree.
+ assertThat(onTouchEventResult2).isTrue();
+ if (handwritingCursorPosition()) {
+ // Cursor is placed at the end of the paragraph containing line 2.
+ verify(mTestView1).setSelection(30);
+ } else {
+ // Since the stylus down point was inside the TextView's bounds, the handwriting
+ // initiator does not need to set the cursor position.
+ verify(mTestView1, never()).setSelection(anyInt());
+ }
}
@Test
@@ -197,6 +242,7 @@
public void onTouchEvent_startHandwriting_when_stylusMove_withinExtendedHWArea() {
mTestView1.setText("hello");
when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4);
+ when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(0);
if (!mInitiateWithoutConnection) {
mHandwritingInitiator.onInputConnectionCreated(mTestView1);
@@ -214,9 +260,14 @@
// Stylus movement within extended HandwritingArea should trigger IMM.startHandwriting once.
verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1);
- // Since the stylus down point was outside the TextView's bounds, the handwriting initiator
- // sets the cursor position.
- verify(mTestView1).setSelection(4);
+ if (handwritingCursorPosition()) {
+ // Cursor is placed at the end of the text.
+ verify(mTestView1).setSelection(5);
+ } else {
+ // Since the stylus down point was outside the TextView's bounds, the handwriting
+ // initiator sets the cursor position.
+ verify(mTestView1).setSelection(4);
+ }
}
@Test
@@ -246,6 +297,8 @@
public void onTouchEvent_startHandwriting_servedViewUpdate_stylusMoveInExtendedHWArea() {
mTestView1.setText("hello");
when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4);
+ when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(0);
+
// The stylus down point is between mTestView1 and mTestView2, but it is within the
// extended handwriting area of both views. It is closer to mTestView1.
final int x1 = sHwArea1.right + HW_BOUNDS_OFFSETS_RIGHT_PX / 2;
@@ -278,9 +331,14 @@
// Handwriting is started for this view since the stylus down point is closest to this
// view.
verify(mHandwritingInitiator).startHandwriting(mTestView1);
- // Since the stylus down point was outside the TextView's bounds, the handwriting initiator
- // sets the cursor position.
- verify(mTestView1).setSelection(4);
+ if (handwritingCursorPosition()) {
+ // Cursor is placed at the end of the text.
+ verify(mTestView1).setSelection(5);
+ } else {
+ // Since the stylus down point was outside the TextView's bounds, the handwriting
+ // initiator sets the cursor position.
+ verify(mTestView1).setSelection(4);
+ }
}
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java
index 76772b7..0897726 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java
@@ -16,6 +16,12 @@
package android.hardware.devicestate;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
@@ -33,6 +39,7 @@
import org.junit.runners.JUnit4;
import java.util.List;
+import java.util.Set;
/**
* Unit tests for {@link DeviceStateInfo}.
@@ -44,11 +51,25 @@
public final class DeviceStateInfoTest {
private static final DeviceState DEVICE_STATE_0 = new DeviceState(
- new DeviceState.Configuration.Builder(0, "STATE_0").build());
+ new DeviceState.Configuration.Builder(0, "STATE_0")
+ .setSystemProperties(
+ Set.of(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS,
+ PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY))
+ .setPhysicalProperties(
+ Set.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED))
+ .build());
private static final DeviceState DEVICE_STATE_1 = new DeviceState(
- new DeviceState.Configuration.Builder(1, "STATE_1").build());
+ new DeviceState.Configuration.Builder(1, "STATE_1")
+ .setSystemProperties(
+ Set.of(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY))
+ .setPhysicalProperties(
+ Set.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN))
+ .build());
private static final DeviceState DEVICE_STATE_2 = new DeviceState(
- new DeviceState.Configuration.Builder(2, "STATE_2").build());
+ new DeviceState.Configuration.Builder(2, "STATE_2")
+ .setSystemProperties(
+ Set.of(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY))
+ .build());
@Test
public void create() {
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
index 68de21f..78d4324 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
@@ -93,4 +93,22 @@
Assert.assertEquals(originalState, new DeviceState(stateConfiguration));
}
+
+ @Test
+ public void writeToParcel_noPhysicalProperties() {
+ final DeviceState originalState = new DeviceState(
+ new DeviceState.Configuration.Builder(0, "TEST_STATE")
+ .setSystemProperties(Set.of(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS,
+ PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST))
+ .build());
+
+ final Parcel parcel = Parcel.obtain();
+ originalState.getConfiguration().writeToParcel(parcel, 0 /* flags */);
+ parcel.setDataPosition(0);
+
+ final DeviceState.Configuration stateConfiguration =
+ DeviceState.Configuration.CREATOR.createFromParcel(parcel);
+
+ Assert.assertEquals(originalState, new DeviceState(stateConfiguration));
+ }
}
diff --git a/data/etc/OWNERS b/data/etc/OWNERS
index 245f216..701d145 100644
--- a/data/etc/OWNERS
+++ b/data/etc/OWNERS
@@ -12,3 +12,4 @@
per-file preinstalled-packages* = file:/MULTIUSER_OWNERS
per-file services.core.protolog.json = file:/services/core/java/com/android/server/wm/OWNERS
+per-file core.protolog.pb = file:/services/core/java/com/android/server/wm/OWNERS
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 0f12438..749f0e1 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -553,6 +553,8 @@
<permission name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT"/>
<!-- Permission required for CTS test - CtsWearableSensingServiceTestCases -->
<permission name="android.permission.MANAGE_WEARABLE_SENSING_SERVICE"/>
+ <!-- Permission required for CTS test - OnDeviceIntelligenceManagerTest -->
+ <permission name="android.permission.USE_ON_DEVICE_INTELLIGENCE" />
<!-- Permission required for CTS test - CtsTelephonyProviderTestCases -->
<permission name="android.permission.WRITE_APN_SETTINGS"/>
<!-- Permission required for GTS test - GtsStatsdHostTestCases -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 539832e..d44033c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -523,8 +523,8 @@
/**
* Whether we should use jump cut for the change transition.
* This normally happens when opening a new secondary with the existing primary using a
- * different split layout. This can be complicated, like from horizontal to vertical split with
- * new split pairs.
+ * different split layout (ratio or direction). This can be complicated, like from horizontal to
+ * vertical split with new split pairs.
* Uses a jump cut animation to simplify.
*/
private boolean shouldUseJumpCutForChangeTransition(@NonNull TransitionInfo info) {
@@ -553,8 +553,8 @@
}
// Check if the transition contains both opening and closing windows.
- boolean hasOpeningWindow = false;
- boolean hasClosingWindow = false;
+ final List<TransitionInfo.Change> openChanges = new ArrayList<>();
+ final List<TransitionInfo.Change> closeChanges = new ArrayList<>();
for (TransitionInfo.Change change : info.getChanges()) {
if (changingChanges.contains(change)) {
continue;
@@ -564,10 +564,30 @@
// No-op if it will be covered by the changing parent window.
continue;
}
- hasOpeningWindow |= TransitionUtil.isOpeningType(change.getMode());
- hasClosingWindow |= TransitionUtil.isClosingType(change.getMode());
+ if (TransitionUtil.isOpeningType(change.getMode())) {
+ openChanges.add(change);
+ } else if (TransitionUtil.isClosingType(change.getMode())) {
+ closeChanges.add(change);
+ }
}
- return hasOpeningWindow && hasClosingWindow;
+ if (openChanges.isEmpty() || closeChanges.isEmpty()) {
+ // Only skip if the transition contains both open and close.
+ return false;
+ }
+ if (changingChanges.size() != 1 || openChanges.size() != 1 || closeChanges.size() != 1) {
+ // Skip when there are too many windows involved.
+ return true;
+ }
+ final TransitionInfo.Change changingChange = changingChanges.get(0);
+ final TransitionInfo.Change openChange = openChanges.get(0);
+ final TransitionInfo.Change closeChange = closeChanges.get(0);
+ if (changingChange.getStartAbsBounds().equals(openChange.getEndAbsBounds())
+ && changingChange.getEndAbsBounds().equals(closeChange.getStartAbsBounds())) {
+ // Don't skip if the transition is a simple shifting without split direction or ratio
+ // change. For example, A|B -> B|C.
+ return false;
+ }
+ return true;
}
/** Updates the changes to end states in {@code startTransaction} for jump cut animation. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 9585842..4455a3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1259,12 +1259,14 @@
* Expands and selects a bubble based on the provided {@link BubbleEntry}. If no bubble
* exists for this entry, and it is able to bubble, a new bubble will be created.
*
- * This is the method to use when opening a bubble via a notification or in a state where
+ * <p>This is the method to use when opening a bubble via a notification or in a state where
* the device might not be unlocked.
*
* @param entry the entry to use for the bubble.
*/
public void expandStackAndSelectBubble(BubbleEntry entry) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "opening bubble from notification key=%s mIsStatusBarShade=%b",
+ entry.getKey(), mIsStatusBarShade);
if (mIsStatusBarShade) {
mNotifEntryToExpandOnShadeUnlock = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 474430e..6524c96 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -449,17 +449,21 @@
@Override
public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target,
- @NonNull MagnetizedObject draggedObject) {
- if (draggedObject.getUnderlyingObject() instanceof View view) {
+ @NonNull MagnetizedObject<?> draggedObject) {
+ Object underlyingObject = draggedObject.getUnderlyingObject();
+ if (underlyingObject instanceof View) {
+ View view = (View) underlyingObject;
animateDismissBubble(view, true);
}
}
@Override
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
- @NonNull MagnetizedObject draggedObject,
+ @NonNull MagnetizedObject<?> draggedObject,
float velX, float velY, boolean wasFlungOut) {
- if (draggedObject.getUnderlyingObject() instanceof View view) {
+ Object underlyingObject = draggedObject.getUnderlyingObject();
+ if (underlyingObject instanceof View) {
+ View view = (View) underlyingObject;
animateDismissBubble(view, false);
if (wasFlungOut) {
@@ -474,7 +478,9 @@
@Override
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target,
@NonNull MagnetizedObject<?> draggedObject) {
- if (draggedObject.getUnderlyingObject() instanceof View view) {
+ Object underlyingObject = draggedObject.getUnderlyingObject();
+ if (underlyingObject instanceof View) {
+ View view = (View) underlyingObject;
mExpandedAnimationController.dismissDraggedOutBubble(
view /* bubble */,
mDismissView.getHeight() /* translationYBy */,
@@ -2474,11 +2480,12 @@
// Let the expanded animation controller know that it shouldn't animate child adds/reorders
// since we're about to animate collapsed.
mExpandedAnimationController.notifyPreparingToCollapse();
-
+ final PointF collapsePosition = mStackAnimationController
+ .getStackPositionAlongNearestHorizontalEdge();
updateOverflowDotVisibility(false /* expanding */);
final Runnable collapseBackToStack = () ->
mExpandedAnimationController.collapseBackToStack(
- mStackAnimationController.getStackPositionAlongNearestHorizontalEdge(),
+ collapsePosition,
/* fadeBubblesDuringCollapse= */ mRemovingLastBubbleWhileExpanded,
() -> {
mBubbleContainer.setActiveController(mStackAnimationController);
@@ -2501,7 +2508,8 @@
}
mExpandedViewAnimationController.reset();
};
- mExpandedViewAnimationController.animateCollapse(collapseBackToStack, after);
+ mExpandedViewAnimationController.animateCollapse(collapseBackToStack, after,
+ collapsePosition);
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
// When the animation completes, we should no longer be showing the content.
// This won't actually update content visibility immediately, if we are currently
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java
index 8a33780..4175529 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java
@@ -15,6 +15,8 @@
*/
package com.android.wm.shell.bubbles.animation;
+import android.graphics.PointF;
+
import com.android.wm.shell.bubbles.BubbleExpandedView;
/**
@@ -55,8 +57,9 @@
* @param startStackCollapse runnable that is triggered when bubbles can start moving back to
* their collapsed location
* @param after runnable to run after animation is complete
+ * @param collapsePosition the position on screen the stack will collapse to
*/
- void animateCollapse(Runnable startStackCollapse, Runnable after);
+ void animateCollapse(Runnable startStackCollapse, Runnable after, PointF collapsePosition);
/**
* Animate the view back to fully expanded state.
@@ -69,6 +72,22 @@
void animateForImeVisibilityChange(boolean visible);
/**
+ * Whether this controller should also animate the expansion for the bubble
+ */
+ boolean shouldAnimateExpansion();
+
+ /**
+ * Animate the expansion of the bubble.
+ *
+ * @param startDelayMillis how long to delay starting the expansion animation
+ * @param after runnable to run after the animation is complete
+ * @param collapsePosition the position on screen the stack will collapse to (and expand from)
+ * @param bubblePosition the position of the bubble on screen that the view is associated with
+ */
+ void animateExpansion(long startDelayMillis, Runnable after, PointF collapsePosition,
+ PointF bubblePosition);
+
+ /**
* Reset the view to fully expanded state
*/
void reset();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
index e43609f..aa4129a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
@@ -28,6 +28,7 @@
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
+import android.graphics.PointF;
import android.view.HapticFeedbackConstants;
import android.view.ViewConfiguration;
@@ -187,9 +188,11 @@
}
@Override
- public void animateCollapse(Runnable startStackCollapse, Runnable after) {
- ProtoLog.d(WM_SHELL_BUBBLES, "expandedView animate collapse swipeVel=%f minFlingVel=%d",
- mSwipeUpVelocity, mMinFlingVelocity);
+ public void animateCollapse(Runnable startStackCollapse, Runnable after,
+ PointF collapsePosition) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "expandedView animate collapse swipeVel=%f minFlingVel=%d"
+ + " collapsePosition=%f,%f", mSwipeUpVelocity, mMinFlingVelocity,
+ collapsePosition.x, collapsePosition.y);
if (mExpandedView != null) {
// Mark it as animating immediately to avoid updates to the view before animation starts
mExpandedView.setAnimating(true);
@@ -274,6 +277,17 @@
}
@Override
+ public boolean shouldAnimateExpansion() {
+ return false;
+ }
+
+ @Override
+ public void animateExpansion(long startDelayMillis, Runnable after, PointF collapsePosition,
+ PointF bubblePosition) {
+ // TODO - animate
+ }
+
+ @Override
public void reset() {
ProtoLog.d(WM_SHELL_BUBBLES, "reset expandedView collapsed state");
if (mExpandedView == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 8b2ec0a..8d489e1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -846,8 +846,10 @@
static ShellController provideShellController(Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
+ DisplayInsetsController displayInsetsController,
@ShellMainThread ShellExecutor mainExecutor) {
- return new ShellController(context, shellInit, shellCommandHandler, mainExecutor);
+ return new ShellController(context, shellInit, shellCommandHandler,
+ displayInsetsController, mainExecutor);
}
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index fb3c35b..04f0f44 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -57,6 +57,8 @@
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
import com.android.wm.shell.dagger.pip.PipModule;
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
+import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -509,6 +511,7 @@
ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
DragToDesktopTransitionHandler dragToDesktopTransitionHandler,
@DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
+ DesktopModeLoggerTransitionObserver desktopModeLoggerTransitionObserver,
LaunchAdjacentController launchAdjacentController,
RecentsTransitionHandler recentsTransitionHandler,
MultiInstanceHelper multiInstanceHelper,
@@ -518,7 +521,8 @@
displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
dragAndDropController, transitions, enterDesktopTransitionHandler,
exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler,
- dragToDesktopTransitionHandler, desktopModeTaskRepository, launchAdjacentController,
+ dragToDesktopTransitionHandler, desktopModeTaskRepository,
+ desktopModeLoggerTransitionObserver, launchAdjacentController,
recentsTransitionHandler, multiInstanceHelper, mainExecutor);
}
@@ -562,6 +566,22 @@
return new DesktopModeTaskRepository();
}
+ @WMSingleton
+ @Provides
+ static DesktopModeLoggerTransitionObserver provideDesktopModeLoggerTransitionObserver(
+ ShellInit shellInit,
+ Transitions transitions,
+ DesktopModeEventLogger desktopModeEventLogger) {
+ return new DesktopModeLoggerTransitionObserver(
+ shellInit, transitions, desktopModeEventLogger);
+ }
+
+ @WMSingleton
+ @Provides
+ static DesktopModeEventLogger provideDesktopModeEventLogger() {
+ return new DesktopModeEventLogger();
+ }
+
//
// Drag and drop
//
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
new file mode 100644
index 0000000..a10c7c0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
@@ -0,0 +1,349 @@
+/*
+ * 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.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityTaskManager.INVALID_TASK_ID
+import android.app.TaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.os.IBinder
+import android.util.SparseArray
+import android.view.SurfaceControl
+import android.view.WindowManager
+import android.window.TransitionInfo
+import androidx.annotation.VisibleForTesting
+import androidx.core.util.containsKey
+import androidx.core.util.forEach
+import androidx.core.util.isEmpty
+import androidx.core.util.isNotEmpty
+import androidx.core.util.plus
+import androidx.core.util.putAll
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.InstanceIdSequence
+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.TaskUpdate
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.shared.TransitionUtil
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.util.KtProtoLog
+
+/**
+ * A [Transitions.TransitionObserver] that observes transitions and the proposed changes to log
+ * appropriate desktop mode session log events. This observes transitions related to desktop mode
+ * and other transitions that originate both within and outside shell.
+ */
+class DesktopModeLoggerTransitionObserver(
+ shellInit: ShellInit,
+ private val transitions: Transitions,
+ private val desktopModeEventLogger: DesktopModeEventLogger
+) : Transitions.TransitionObserver {
+
+ private val idSequence: InstanceIdSequence by lazy { InstanceIdSequence(Int.MAX_VALUE) }
+
+ init {
+ if (Transitions.ENABLE_SHELL_TRANSITIONS && DesktopModeStatus.isEnabled()) {
+ shellInit.addInitCallback(this::onInit, this)
+ }
+ }
+
+ // A sparse array of visible freeform tasks and taskInfos
+ private val visibleFreeformTaskInfos: SparseArray<TaskInfo> = SparseArray()
+
+ // Caching the taskInfos to handle canceled recents animations, if we identify that the recents
+ // animation was cancelled, we restore these tasks to calculate the post-Transition state
+ private val tasksSavedForRecents: SparseArray<TaskInfo> = SparseArray()
+
+ // The instanceId for the current logging session
+ private var loggerInstanceId: InstanceId? = null
+
+ private val isSessionActive: Boolean
+ get() = loggerInstanceId != null
+
+ private fun setSessionInactive() {
+ loggerInstanceId = null
+ }
+
+ fun onInit() {
+ transitions.registerObserver(this)
+ }
+
+ override fun onTransitionReady(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction
+ ) {
+ // this was a new recents animation
+ if (info.isRecentsTransition() && tasksSavedForRecents.isEmpty()) {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: Recents animation running, saving tasks for later"
+ )
+ // TODO (b/326391303) - avoid logging session exit if we can identify a cancelled
+ // recents animation
+
+ // when recents animation is running, all freeform tasks are sent TO_BACK temporarily
+ // if the user ends up at home, we need to update the visible freeform tasks
+ // if the user cancels the animation, the subsequent transition is NONE
+ // if the user opens a new task, the subsequent transition is OPEN with flag
+ tasksSavedForRecents.putAll(visibleFreeformTaskInfos)
+ }
+
+ // figure out what the new state of freeform tasks would be post transition
+ var postTransitionVisibleFreeformTasks = getPostTransitionVisibleFreeformTaskInfos(info)
+
+ // A canceled recents animation is followed by a TRANSIT_NONE transition with no flags, if
+ // that's the case, we might have accidentally logged a session exit and would need to
+ // revaluate again. Add all the tasks back.
+ // This will start a new desktop mode session.
+ if (
+ info.type == WindowManager.TRANSIT_NONE &&
+ info.flags == 0 &&
+ tasksSavedForRecents.isNotEmpty()
+ ) {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: Canceled recents animation, restoring tasks"
+ )
+ // restore saved tasks in the updated set and clear for next use
+ postTransitionVisibleFreeformTasks += tasksSavedForRecents
+ tasksSavedForRecents.clear()
+ }
+
+ // identify if we need to log any changes and update the state of visible freeform tasks
+ identifyLogEventAndUpdateState(
+ transitionInfo = info,
+ preTransitionVisibleFreeformTasks = visibleFreeformTaskInfos,
+ postTransitionVisibleFreeformTasks = postTransitionVisibleFreeformTasks
+ )
+ }
+
+ override fun onTransitionStarting(transition: IBinder) {}
+
+ override fun onTransitionMerged(merged: IBinder, playing: IBinder) {}
+
+ override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {}
+
+ private fun getPostTransitionVisibleFreeformTaskInfos(
+ info: TransitionInfo
+ ): SparseArray<TaskInfo> {
+ // device is sleeping, so no task will be visible anymore
+ if (info.type == WindowManager.TRANSIT_SLEEP) {
+ return SparseArray()
+ }
+
+ // filter changes involving freeform tasks or tasks that were cached in previous state
+ val changesToFreeformWindows =
+ info.changes
+ .filter { it.taskInfo != null && it.requireTaskInfo().taskId != INVALID_TASK_ID }
+ .filter {
+ it.requireTaskInfo().isFreeformWindow() ||
+ visibleFreeformTaskInfos.containsKey(it.requireTaskInfo().taskId)
+ }
+
+ val postTransitionFreeformTasks: SparseArray<TaskInfo> = SparseArray()
+ // start off by adding all existing tasks
+ postTransitionFreeformTasks.putAll(visibleFreeformTaskInfos)
+
+ // the combined set of taskInfos we are interested in this transition change
+ for (change in changesToFreeformWindows) {
+ val taskInfo = change.requireTaskInfo()
+
+ // check if this task existed as freeform window in previous cached state and it's now
+ // changing window modes
+ if (
+ visibleFreeformTaskInfos.containsKey(taskInfo.taskId) &&
+ visibleFreeformTaskInfos.get(taskInfo.taskId).isFreeformWindow() &&
+ !taskInfo.isFreeformWindow()
+ ) {
+ postTransitionFreeformTasks.remove(taskInfo.taskId)
+ // no need to evaluate new visibility of this task, since it's no longer a freeform
+ // window
+ continue
+ }
+
+ // check if the task is visible after this change, otherwise remove it
+ if (isTaskVisibleAfterChange(change)) {
+ postTransitionFreeformTasks.put(taskInfo.taskId, taskInfo)
+ } else {
+ postTransitionFreeformTasks.remove(taskInfo.taskId)
+ }
+ }
+
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: taskInfo map after processing changes %s",
+ postTransitionFreeformTasks.size()
+ )
+
+ return postTransitionFreeformTasks
+ }
+
+ /**
+ * Look at the [TransitionInfo.Change] and figure out if this task will be visible after this
+ * change is processed
+ */
+ private fun isTaskVisibleAfterChange(change: TransitionInfo.Change): Boolean =
+ when {
+ TransitionUtil.isOpeningType(change.mode) -> true
+ TransitionUtil.isClosingType(change.mode) -> false
+ // change mode TRANSIT_CHANGE is only for visible to visible transitions
+ change.mode == WindowManager.TRANSIT_CHANGE -> true
+ else -> false
+ }
+
+ /**
+ * Log the appropriate log event based on the new state of TasksInfos and previously cached
+ * state and update it
+ */
+ private fun identifyLogEventAndUpdateState(
+ transitionInfo: TransitionInfo,
+ preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
+ postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>
+ ) {
+ if (
+ postTransitionVisibleFreeformTasks.isEmpty() &&
+ preTransitionVisibleFreeformTasks.isNotEmpty() &&
+ isSessionActive
+ ) {
+ // Sessions is finishing, log task updates followed by an exit event
+ identifyAndLogTaskUpdates(
+ loggerInstanceId!!.id,
+ preTransitionVisibleFreeformTasks,
+ postTransitionVisibleFreeformTasks
+ )
+
+ desktopModeEventLogger.logSessionExit(
+ loggerInstanceId!!.id,
+ getExitReason(transitionInfo)
+ )
+
+ setSessionInactive()
+ } else if (
+ postTransitionVisibleFreeformTasks.isNotEmpty() &&
+ preTransitionVisibleFreeformTasks.isEmpty() &&
+ !isSessionActive
+ ) {
+ // Session is starting, log enter event followed by task updates
+ loggerInstanceId = idSequence.newInstanceId()
+ desktopModeEventLogger.logSessionEnter(
+ loggerInstanceId!!.id,
+ getEnterReason(transitionInfo)
+ )
+
+ identifyAndLogTaskUpdates(
+ loggerInstanceId!!.id,
+ preTransitionVisibleFreeformTasks,
+ postTransitionVisibleFreeformTasks
+ )
+ } else if (isSessionActive) {
+ // Session is neither starting, nor finishing, log task updates if there are any
+ identifyAndLogTaskUpdates(
+ loggerInstanceId!!.id,
+ preTransitionVisibleFreeformTasks,
+ postTransitionVisibleFreeformTasks
+ )
+ }
+
+ // update the state to the new version
+ visibleFreeformTaskInfos.clear()
+ visibleFreeformTaskInfos.putAll(postTransitionVisibleFreeformTasks)
+ }
+
+ // TODO(b/326231724) - Add logging around taskInfoChanges Updates
+ /** Compare the old and new state of taskInfos and identify and log the changes */
+ private fun identifyAndLogTaskUpdates(
+ sessionId: Int,
+ preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
+ postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>
+ ) {
+ // find new tasks that were added
+ postTransitionVisibleFreeformTasks.forEach { taskId, taskInfo ->
+ if (!preTransitionVisibleFreeformTasks.containsKey(taskId)) {
+ desktopModeEventLogger.logTaskAdded(sessionId, buildTaskUpdateForTask(taskInfo))
+ }
+ }
+
+ // find old tasks that were removed
+ preTransitionVisibleFreeformTasks.forEach { taskId, taskInfo ->
+ if (!postTransitionVisibleFreeformTasks.containsKey(taskId)) {
+ desktopModeEventLogger.logTaskRemoved(sessionId, buildTaskUpdateForTask(taskInfo))
+ }
+ }
+ }
+
+ // TODO(b/326231724: figure out how to get taskWidth and taskHeight from TaskInfo
+ private fun buildTaskUpdateForTask(taskInfo: TaskInfo): TaskUpdate {
+ val taskUpdate = TaskUpdate(taskInfo.taskId, taskInfo.userId)
+ // add task x, y if available
+ taskInfo.positionInParent?.let { taskUpdate.copy(taskX = it.x, taskY = it.y) }
+
+ return taskUpdate
+ }
+
+ /** Get [EnterReason] for this session enter */
+ private fun getEnterReason(transitionInfo: TransitionInfo): EnterReason {
+ // TODO(b/326231756) - Add support for missing enter reasons
+ return when (transitionInfo.type) {
+ WindowManager.TRANSIT_WAKE -> EnterReason.SCREEN_ON
+ Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP -> EnterReason.APP_HANDLE_DRAG
+ Transitions.TRANSIT_MOVE_TO_DESKTOP -> EnterReason.APP_HANDLE_MENU_BUTTON
+ WindowManager.TRANSIT_OPEN -> EnterReason.APP_FREEFORM_INTENT
+ else -> EnterReason.UNKNOWN_ENTER
+ }
+ }
+
+ /** Get [ExitReason] for this session exit */
+ private fun getExitReason(transitionInfo: TransitionInfo): ExitReason {
+ // TODO(b/326231756) - Add support for missing exit reasons
+ return when {
+ transitionInfo.type == WindowManager.TRANSIT_SLEEP -> ExitReason.SCREEN_OFF
+ transitionInfo.type == WindowManager.TRANSIT_CLOSE -> ExitReason.TASK_FINISHED
+ transitionInfo.type == Transitions.TRANSIT_EXIT_DESKTOP_MODE -> ExitReason.DRAG_TO_EXIT
+ transitionInfo.isRecentsTransition() -> ExitReason.RETURN_HOME_OR_OVERVIEW
+ else -> ExitReason.UNKNOWN_EXIT
+ }
+ }
+
+ /** Adds tasks to the saved copy of freeform taskId, taskInfo. Only used for testing. */
+ @VisibleForTesting
+ fun addTaskInfosToCachedMap(taskInfo: TaskInfo) {
+ visibleFreeformTaskInfos.set(taskInfo.taskId, taskInfo)
+ }
+
+ @VisibleForTesting fun getLoggerSessionId(): Int? = loggerInstanceId?.id
+
+ @VisibleForTesting
+ fun setLoggerSessionId(id: Int) {
+ loggerInstanceId = InstanceId.fakeInstanceId(id)
+ }
+
+ private fun TransitionInfo.Change.requireTaskInfo(): RunningTaskInfo {
+ return this.taskInfo ?: throw IllegalStateException("Expected TaskInfo in the Change")
+ }
+
+ private fun TaskInfo.isFreeformWindow(): Boolean {
+ return this.windowingMode == WINDOWING_MODE_FREEFORM
+ }
+
+ private fun TransitionInfo.isRecentsTransition(): Boolean {
+ return this.type == WindowManager.TRANSIT_TO_FRONT &&
+ this.flags == WindowManager.TRANSIT_FLAG_IS_RECENTS
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index fb0ed15..6a3c8d2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -138,9 +138,9 @@
@WindowConfiguration.WindowingMode int windowingMode, int captionHeight) {
final Region region = new Region();
int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM
- ? 2 * layout.stableInsets().top
- : mContext.getResources().getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height);
+ ? mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height)
+ : 2 * layout.stableInsets().top;
// A thin, short Rect at the top of the screen.
if (windowingMode == WINDOWING_MODE_FREEFORM) {
int fromFreeformWidth = mContext.getResources().getDimensionPixelSize(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 95237c3..4a3130f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -78,6 +78,7 @@
import com.android.wm.shell.transition.OneShotRemoteHandler
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.util.KtProtoLog
+import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
import java.io.PrintWriter
@@ -102,6 +103,7 @@
ToggleResizeDesktopTaskTransitionHandler,
private val dragToDesktopTransitionHandler: DragToDesktopTransitionHandler,
private val desktopModeTaskRepository: DesktopModeTaskRepository,
+ private val desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver,
private val launchAdjacentController: LaunchAdjacentController,
private val recentsTransitionHandler: RecentsTransitionHandler,
private val multiInstanceHelper: MultiInstanceHelper,
@@ -959,7 +961,8 @@
}
/**
- * Perform checks required on drag end. Move to fullscreen if drag ends in status bar area.
+ * Perform checks required on drag end. If indicator indicates a windowing mode change, perform
+ * that change. Otherwise, ensure bounds are up to date.
*
* @param taskInfo the task being dragged.
* @param position position of surface when drag ends.
@@ -970,7 +973,8 @@
taskInfo: RunningTaskInfo,
position: Point,
inputCoordinate: PointF,
- taskBounds: Rect
+ taskBounds: Rect,
+ validDragArea: Rect
) {
if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) {
return
@@ -993,10 +997,21 @@
releaseVisualIndicator()
snapToHalfScreen(taskInfo, SnapPosition.RIGHT)
}
- DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR,
DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR -> {
+ // If task bounds are outside valid drag area, snap them inward and perform a
+ // transaction to set bounds.
+ if (DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(
+ taskBounds, validDragArea)) {
+ val wct = WindowContainerTransaction()
+ wct.setBounds(taskInfo.token, taskBounds)
+ transitions.startTransition(TRANSIT_CHANGE, wct, null)
+ }
releaseVisualIndicator()
}
+ DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR -> {
+ throw IllegalArgumentException("Should not be receiving TO_DESKTOP_INDICATOR for " +
+ "a freeform task.")
+ }
}
// A freeform drag-move ended, remove the indicator immediately.
releaseVisualIndicator()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index f8b5530..235456c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -544,7 +544,8 @@
apps.add(target);
} else {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- " unhandled change taskId=%d", taskInfo.taskId);
+ " unhandled change taskId=%d",
+ taskInfo != null ? taskInfo.taskId : -1);
}
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 952e2d4..86c8f04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -436,7 +436,11 @@
}
public void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
- mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
+ if (ENABLE_SHELL_TRANSITIONS) {
+ mStageCoordinator.dismissSplitScreen(toTopTaskId, exitReason);
+ } else {
+ mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
+ }
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
index 7f16c5e..af11ebc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.splitscreen;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_UNKNOWN;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -45,6 +46,8 @@
return runSetSideStagePosition(args, pw);
case "switchSplitPosition":
return runSwitchSplitPosition();
+ case "exitSplitScreen":
+ return runExitSplitScreen(args, pw);
default:
pw.println("Invalid command: " + args[0]);
return false;
@@ -91,6 +94,17 @@
return true;
}
+ private boolean runExitSplitScreen(String[] args, PrintWriter pw) {
+ if (args.length < 2) {
+ // First argument is the action name.
+ pw.println("Error: task id should be provided as arguments");
+ return false;
+ }
+ final int taskId = Integer.parseInt(args[1]);
+ mController.exitSplitScreen(taskId, EXIT_REASON_UNKNOWN);
+ return true;
+ }
+
@Override
public void printShellCommandHelp(PrintWriter pw, String prefix) {
pw.println(prefix + "moveToSideStage <taskId> <SideStagePosition>");
@@ -101,5 +115,7 @@
pw.println(prefix + " Sets the position of the side-stage.");
pw.println(prefix + "switchSplitPosition");
pw.println(prefix + " Reverses the split.");
+ pw.println(prefix + "exitSplitScreen <taskId>");
+ pw.println(prefix + " Exits split screen and leaves the provided split task on top.");
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 9dd4c19..36368df9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -29,6 +29,7 @@
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -1450,6 +1451,7 @@
mExitSplitScreenOnHide = exitSplitScreenOnHide;
}
+ /** Exits split screen with legacy transition */
void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitSplitScreen: topTaskId=%d reason=%s active=%b",
toTopTaskId, exitReasonToString(exitReason), mMainStage.isActive());
@@ -1469,6 +1471,7 @@
applyExitSplitScreen(childrenToTop, wct, exitReason);
}
+ /** Exits split screen with legacy transition */
private void exitSplitScreen(@Nullable StageTaskListener childrenToTop,
@ExitReason int exitReason) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitSplitScreen: mainStageToTop=%b reason=%s active=%b",
@@ -1546,6 +1549,14 @@
}
}
+ void dismissSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
+ if (!mMainStage.isActive()) return;
+ final int stage = getStageOfTask(toTopTaskId);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ prepareExitSplitScreen(stage, wct);
+ mSplitTransitions.startDismissTransition(wct, this, stage, exitReason);
+ }
+
/**
* Overridden by child classes.
*/
@@ -1611,6 +1622,8 @@
// User has used a keyboard shortcut to go back to fullscreen from split
case EXIT_REASON_DESKTOP_MODE:
// One of the children enters desktop mode
+ case EXIT_REASON_UNKNOWN:
+ // Unknown reason
return true;
default:
return false;
@@ -2776,7 +2789,7 @@
+ " with " + taskInfo.taskId + " before startAnimation().");
record.addRecord(stage, true, taskInfo.taskId);
}
- } else if (isClosingType(change.getMode())) {
+ } else if (change.getMode() == TRANSIT_CLOSE) {
if (stage.containsTask(taskInfo.taskId)) {
record.addRecord(stage, false, taskInfo.taskId);
Log.w(TAG, "Expected onTaskVanished on " + stage + " to have been called"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index e2be153..1ce87ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -22,6 +22,7 @@
import static android.window.StartingWindowRemovalInfo.DEFER_MODE_ROTATION;
import android.annotation.CallSuper;
+import android.annotation.NonNull;
import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.content.Context;
@@ -306,7 +307,7 @@
@CallSuper
protected void removeImmediately() {
mRemoveExecutor.removeCallbacks(mScheduledRunnable);
- mRecordManager.onRecordRemoved(mTaskId);
+ mRecordManager.onRecordRemoved(this, mTaskId);
}
}
@@ -327,6 +328,11 @@
}
void addRecord(int taskId, StartingWindowRecord record) {
+ final StartingWindowRecord original = mStartingWindowRecords.get(taskId);
+ if (original != null) {
+ mTmpRemovalInfo.taskId = taskId;
+ original.removeIfPossible(mTmpRemovalInfo, true /* immediately */);
+ }
mStartingWindowRecords.put(taskId, record);
}
@@ -346,8 +352,11 @@
removeWindow(mTmpRemovalInfo, true/* immediately */);
}
- void onRecordRemoved(int taskId) {
- mStartingWindowRecords.remove(taskId);
+ void onRecordRemoved(@NonNull StartingWindowRecord record, int taskId) {
+ final StartingWindowRecord currentRecord = mStartingWindowRecords.get(taskId);
+ if (currentRecord == record) {
+ mStartingWindowRecords.remove(taskId);
+ }
}
StartingWindowRecord getRecord(int taskId) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/DisplayImeChangeListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/DisplayImeChangeListener.java
new file mode 100644
index 0000000..a94f802
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/DisplayImeChangeListener.java
@@ -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.wm.shell.sysui;
+
+import android.graphics.Rect;
+
+/**
+ * Callbacks for when the Display IME changes.
+ */
+public interface DisplayImeChangeListener {
+ /**
+ * Called when the ime bounds change.
+ */
+ default void onImeBoundsChanged(int displayId, Rect bounds) {}
+
+ /**
+ * Called when the IME visibility change.
+ */
+ default void onImeVisibilityChanged(int displayId, boolean isShowing) {}
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
index a7843e2..2f6edc2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
@@ -30,21 +30,28 @@
import android.content.pm.ActivityInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.graphics.Rect;
import android.os.Bundle;
import android.util.ArrayMap;
+import android.view.InsetsSource;
+import android.view.InsetsState;
import android.view.SurfaceControlRegistry;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ExternalThread;
import java.io.PrintWriter;
import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
import java.util.function.Supplier;
/**
@@ -57,6 +64,7 @@
private final ShellInit mShellInit;
private final ShellCommandHandler mShellCommandHandler;
private final ShellExecutor mMainExecutor;
+ private final DisplayInsetsController mDisplayInsetsController;
private final ShellInterfaceImpl mImpl = new ShellInterfaceImpl();
private final CopyOnWriteArrayList<ConfigurationChangeListener> mConfigChangeListeners =
@@ -65,6 +73,8 @@
new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<UserChangeListener> mUserChangeListeners =
new CopyOnWriteArrayList<>();
+ private final ConcurrentHashMap<DisplayImeChangeListener, Executor> mDisplayImeChangeListeners =
+ new ConcurrentHashMap<>();
private ArrayMap<String, Supplier<ExternalInterfaceBinder>> mExternalInterfaceSuppliers =
new ArrayMap<>();
@@ -73,20 +83,53 @@
private Configuration mLastConfiguration;
+ private OnInsetsChangedListener mInsetsChangeListener = new OnInsetsChangedListener() {
+ private InsetsState mInsetsState = new InsetsState();
+
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ if (mInsetsState == insetsState) {
+ return;
+ }
+
+ InsetsSource oldSource = mInsetsState.peekSource(InsetsSource.ID_IME);
+ boolean wasVisible = (oldSource != null && oldSource.isVisible());
+ Rect oldFrame = wasVisible ? oldSource.getFrame() : null;
+
+ InsetsSource newSource = insetsState.peekSource(InsetsSource.ID_IME);
+ boolean isVisible = (newSource != null && newSource.isVisible());
+ Rect newFrame = isVisible ? newSource.getFrame() : null;
+
+ if (wasVisible != isVisible) {
+ onImeVisibilityChanged(isVisible);
+ }
+
+ if (newFrame != null && !newFrame.equals(oldFrame)) {
+ onImeBoundsChanged(newFrame);
+ }
+
+ mInsetsState = insetsState;
+ }
+ };
+
public ShellController(Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
+ DisplayInsetsController displayInsetsController,
ShellExecutor mainExecutor) {
mContext = context;
mShellInit = shellInit;
mShellCommandHandler = shellCommandHandler;
+ mDisplayInsetsController = displayInsetsController;
mMainExecutor = mainExecutor;
shellInit.addInitCallback(this::onInit, this);
}
private void onInit() {
mShellCommandHandler.addDumpCallback(this::dump, this);
+ mDisplayInsetsController.addInsetsChangedListener(
+ mContext.getDisplayId(), mInsetsChangeListener);
}
/**
@@ -259,6 +302,25 @@
}
}
+ @VisibleForTesting
+ void onImeBoundsChanged(Rect bounds) {
+ ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Display Ime bounds changed");
+ mDisplayImeChangeListeners.forEach(
+ (DisplayImeChangeListener listener, Executor executor) ->
+ executor.execute(() -> listener.onImeBoundsChanged(
+ mContext.getDisplayId(), bounds)));
+ }
+
+ @VisibleForTesting
+ void onImeVisibilityChanged(boolean isShowing) {
+ ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Display Ime visibility changed: isShowing=%b",
+ isShowing);
+ mDisplayImeChangeListeners.forEach(
+ (DisplayImeChangeListener listener, Executor executor) ->
+ executor.execute(() -> listener.onImeVisibilityChanged(
+ mContext.getDisplayId(), isShowing)));
+ }
+
private void handleInit() {
SurfaceControlRegistry.createProcessInstance(mContext);
mShellInit.init();
@@ -329,6 +391,19 @@
}
@Override
+ public void addDisplayImeChangeListener(DisplayImeChangeListener listener,
+ Executor executor) {
+ ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Adding new DisplayImeChangeListener");
+ mDisplayImeChangeListeners.put(listener, executor);
+ }
+
+ @Override
+ public void removeDisplayImeChangeListener(DisplayImeChangeListener listener) {
+ ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Removing DisplayImeChangeListener");
+ mDisplayImeChangeListeners.remove(listener);
+ }
+
+ @Override
public boolean handleCommand(String[] args, PrintWriter pw) {
try {
boolean[] result = new boolean[1];
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
index bc5dd11..bd1c64a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
@@ -25,6 +25,7 @@
import java.io.PrintWriter;
import java.util.List;
+import java.util.concurrent.Executor;
/**
* General interface for notifying the Shell of common SysUI events like configuration or keyguard
@@ -65,6 +66,18 @@
default void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {}
/**
+ * Registers a DisplayImeChangeListener to monitor for changes on Ime
+ * position and visibility.
+ */
+ default void addDisplayImeChangeListener(DisplayImeChangeListener listener,
+ Executor executor) {}
+
+ /**
+ * Removes a registered DisplayImeChangeListener.
+ */
+ default void removeDisplayImeChangeListener(DisplayImeChangeListener listener) {}
+
+ /**
* Handles a shell command.
*/
default boolean handleCommand(final String[] args, PrintWriter pw) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index b2eeea7..c59a1b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -19,9 +19,11 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
+import android.graphics.Rect;
import android.os.Handler;
import android.util.SparseArray;
import android.view.Choreographer;
@@ -186,7 +188,7 @@
final FluidResizeTaskPositioner taskPositioner =
new FluidResizeTaskPositioner(mTaskOrganizer, mTransitions, windowDecoration,
- mDisplayController, 0 /* disallowedAreaForEndBoundsHeight */);
+ mDisplayController);
final CaptionTouchEventListener touchEventListener =
new CaptionTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
@@ -286,8 +288,15 @@
mDragPointerId = e.getPointerId(0);
}
final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
- mDragPositioningCallback.onDragPositioningEnd(
+ final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
+ DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(newTaskBounds,
+ mWindowDecorByTaskId.get(mTaskId).calculateValidDragArea());
+ if (newTaskBounds != taskInfo.configuration.windowConfiguration.getBounds()) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setBounds(taskInfo.token, newTaskBounds);
+ mTransitions.startTransition(TRANSIT_CHANGE, wct, null);
+ }
final boolean wasDragging = mIsDragging;
mIsDragging = false;
return wasDragging;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 91e9601..9a48922 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.windowdecor;
+import android.annotation.NonNull;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration;
import android.app.WindowConfiguration.WindowingMode;
@@ -87,6 +88,7 @@
}
@Override
+ @NonNull
Rect calculateValidDragArea() {
final int leftButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
R.dimen.caption_left_buttons_width);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 9c01442..918cefb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -639,7 +639,7 @@
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
mDesktopTasksController.onDragPositioningEnd(taskInfo, position,
new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
- newTaskBounds);
+ newTaskBounds, decoration.calculateValidDragArea());
if (touchingButton && !mHasLongClicked) {
// We need the input event to not be consumed here to end the ripple
// effect on the touched button. We will reset drag state in the ensuing
@@ -866,6 +866,13 @@
return;
}
if (mTransitionDragActive) {
+ // Do not create an indicator at all if we're not past transition height.
+ DisplayLayout layout = mDisplayController
+ .getDisplayLayout(relevantDecor.mTaskInfo.displayId);
+ if (ev.getRawY() < 2 * layout.stableInsets().top
+ && mMoveToDesktopAnimator == null) {
+ return;
+ }
final DesktopModeVisualIndicator.IndicatorType indicatorType =
mDesktopTasksController.updateVisualIndicator(
relevantDecor.mTaskInfo,
@@ -1080,18 +1087,16 @@
windowDecoration.createResizeVeil();
final DragPositioningCallback dragPositioningCallback;
- final int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
- R.dimen.desktop_mode_fullscreen_from_desktop_height);
if (!DesktopModeStatus.isVeiledResizeEnabled()) {
dragPositioningCallback = new FluidResizeTaskPositioner(
mTaskOrganizer, mTransitions, windowDecoration, mDisplayController,
- mDragStartListener, mTransactionFactory, transitionAreaHeight);
+ mDragStartListener, mTransactionFactory);
windowDecoration.setTaskDragResizer(
(FluidResizeTaskPositioner) dragPositioningCallback);
} else {
dragPositioningCallback = new VeiledResizeTaskPositioner(
mTaskOrganizer, windowDecoration, mDisplayController,
- mDragStartListener, mTransitions, transitionAreaHeight);
+ mDragStartListener, mTransitions);
windowDecoration.setTaskDragResizer(
(VeiledResizeTaskPositioner) dragPositioningCallback);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index c9669a7..4c9e171 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -22,6 +22,7 @@
import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
@@ -499,6 +500,7 @@
* Determine valid drag area for this task based on elements in the app chip.
*/
@Override
+ @NonNull
Rect calculateValidDragArea() {
final int appTextWidth = ((DesktopModeAppControlsWindowDecorationViewHolder)
mWindowDecorViewHolder).getAppNameTextWidth();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index 5afbd54..82c399a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -131,7 +131,7 @@
t.setPosition(decoration.mTaskSurface, repositionTaskBounds.left, repositionTaskBounds.top);
}
- private static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
+ static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
PointF repositionStartPoint, float x, float y) {
final float deltaX = x - repositionStartPoint.x;
final float deltaY = y - repositionStartPoint.y;
@@ -140,49 +140,32 @@
}
/**
- * Calculates the new position of the top edge of the task and returns true if it is below the
- * disallowed area.
- *
- * @param disallowedAreaForEndBoundsHeight the height of the area that where the task positioner
- * should not finalize the bounds using WCT#setBounds
- * @param taskBoundsAtDragStart the bounds of the task on the first drag input event
- * @param repositionStartPoint initial input coordinate
- * @param y the y position of the motion event
- * @return true if the top of the task is below the disallowed area
+ * If task bounds are outside of provided drag area, snap the bounds to be just inside the
+ * drag area.
+ * @param repositionTaskBounds bounds determined by task positioner
+ * @param validDragArea the area that task must be positioned inside
+ * @return whether bounds were modified
*/
- static boolean isBelowDisallowedArea(int disallowedAreaForEndBoundsHeight,
- Rect taskBoundsAtDragStart, PointF repositionStartPoint, float y) {
- final float deltaY = y - repositionStartPoint.y;
- final float topPosition = taskBoundsAtDragStart.top + deltaY;
- return topPosition > disallowedAreaForEndBoundsHeight;
- }
-
- /**
- * Updates repositionTaskBounds to the final bounds of the task after the drag is finished. If
- * the bounds are outside of the valid drag area, the task is shifted back onto the edge of the
- * valid drag area.
- */
- static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
- PointF repositionStartPoint, float x, float y, Rect validDragArea) {
- updateTaskBounds(repositionTaskBounds, taskBoundsAtDragStart, repositionStartPoint,
- x, y);
- snapTaskBoundsIfNecessary(repositionTaskBounds, validDragArea);
- }
-
- private static void snapTaskBoundsIfNecessary(Rect repositionTaskBounds, Rect validDragArea) {
+ public static boolean snapTaskBoundsIfNecessary(Rect repositionTaskBounds, Rect validDragArea) {
// If we were never supplied a valid drag area, do not restrict movement.
// Otherwise, we restrict deltas to keep task position inside the Rect.
- if (validDragArea.width() == 0) return;
+ if (validDragArea.width() == 0) return false;
+ boolean result = false;
if (repositionTaskBounds.left < validDragArea.left) {
repositionTaskBounds.offset(validDragArea.left - repositionTaskBounds.left, 0);
+ result = true;
} else if (repositionTaskBounds.left > validDragArea.right) {
repositionTaskBounds.offset(validDragArea.right - repositionTaskBounds.left, 0);
+ result = true;
}
if (repositionTaskBounds.top < validDragArea.top) {
repositionTaskBounds.offset(0, validDragArea.top - repositionTaskBounds.top);
+ result = true;
} else if (repositionTaskBounds.top > validDragArea.bottom) {
repositionTaskBounds.offset(0, validDragArea.bottom - repositionTaskBounds.top);
+ result = true;
}
+ return result;
}
private static float getMinWidth(DisplayController displayController,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 6bfc7cd..6f8b3d5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -60,9 +60,6 @@
private final Rect mTaskBoundsAtDragStart = new Rect();
private final PointF mRepositionStartPoint = new PointF();
private final Rect mRepositionTaskBounds = new Rect();
- // If a task move (not resize) finishes with the positions y less than this value, do not
- // finalize the bounds there using WCT#setBounds
- private final int mDisallowedAreaForEndBoundsHeight;
private boolean mHasDragResized;
private boolean mIsResizingOrAnimatingResize;
private int mCtrlType;
@@ -70,11 +67,9 @@
@Surface.Rotation private int mRotation;
FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, Transitions transitions,
- WindowDecoration windowDecoration, DisplayController displayController,
- int disallowedAreaForEndBoundsHeight) {
+ WindowDecoration windowDecoration, DisplayController displayController) {
this(taskOrganizer, transitions, windowDecoration, displayController,
- dragStartListener -> {}, SurfaceControl.Transaction::new,
- disallowedAreaForEndBoundsHeight);
+ dragStartListener -> {}, SurfaceControl.Transaction::new);
}
FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
@@ -82,15 +77,13 @@
WindowDecoration windowDecoration,
DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
- Supplier<SurfaceControl.Transaction> supplier,
- int disallowedAreaForEndBoundsHeight) {
+ Supplier<SurfaceControl.Transaction> supplier) {
mTaskOrganizer = taskOrganizer;
mTransitions = transitions;
mWindowDecoration = windowDecoration;
mDisplayController = displayController;
mDragStartListener = dragStartListener;
mTransactionSupplier = supplier;
- mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight;
}
@Override
@@ -157,14 +150,10 @@
wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
}
mDragResizeEndTransition = mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
- } else if (mCtrlType == CTRL_TYPE_UNDEFINED
- && DragPositioningCallbackUtility.isBelowDisallowedArea(
- mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
- y)) {
+ } else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
- mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
- mWindowDecoration.calculateValidDragArea());
+ DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
+ mTaskBoundsAtDragStart, mRepositionStartPoint, x, y);
wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 5c69d55..c12a93e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -54,9 +54,6 @@
private final Rect mTaskBoundsAtDragStart = new Rect();
private final PointF mRepositionStartPoint = new PointF();
private final Rect mRepositionTaskBounds = new Rect();
- // If a task move (not resize) finishes with the positions y less than this value, do not
- // finalize the bounds there using WCT#setBounds
- private final int mDisallowedAreaForEndBoundsHeight;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
private int mCtrlType;
private boolean mIsResizingOrAnimatingResize;
@@ -66,25 +63,22 @@
DesktopModeWindowDecoration windowDecoration,
DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
- Transitions transitions,
- int disallowedAreaForEndBoundsHeight) {
+ Transitions transitions) {
this(taskOrganizer, windowDecoration, displayController, dragStartListener,
- SurfaceControl.Transaction::new, transitions, disallowedAreaForEndBoundsHeight);
+ SurfaceControl.Transaction::new, transitions);
}
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
DesktopModeWindowDecoration windowDecoration,
DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
- Supplier<SurfaceControl.Transaction> supplier, Transitions transitions,
- int disallowedAreaForEndBoundsHeight) {
+ Supplier<SurfaceControl.Transaction> supplier, Transitions transitions) {
mDesktopWindowDecoration = windowDecoration;
mTaskOrganizer = taskOrganizer;
mDisplayController = displayController;
mDragStartListener = dragStartListener;
mTransactionSupplier = supplier;
mTransitions = transitions;
- mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight;
}
@Override
@@ -151,13 +145,10 @@
// won't be called.
resetVeilIfVisible();
}
- } else if (DragPositioningCallbackUtility.isBelowDisallowedArea(
- mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
- y)) {
+ } else {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
- mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
- mDesktopWindowDecoration.calculateValidDragArea());
+ DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
+ mTaskBoundsAtDragStart, mRepositionStartPoint, x, y);
wct.setBounds(mDesktopWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
index ae39fbc..4a4c5e8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -37,6 +37,7 @@
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView
import com.android.wm.shell.bubbles.properties.BubbleProperties
import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayInsetsController
import com.android.wm.shell.common.FloatingContentCoordinator
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
@@ -94,7 +95,8 @@
val windowManager = context.getSystemService(WindowManager::class.java)
val shellInit = ShellInit(mainExecutor)
val shellCommandHandler = ShellCommandHandler()
- val shellController = ShellController(context, shellInit, shellCommandHandler, mainExecutor)
+ val shellController = ShellController(context, shellInit, shellCommandHandler,
+ mock<DisplayInsetsController>(), mainExecutor)
bubblePositioner = BubblePositioner(context, windowManager)
val bubbleData =
BubbleData(
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
new file mode 100644
index 0000000..65117f7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
@@ -0,0 +1,358 @@
+/*
+ * 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.desktopmode
+
+import android.app.ActivityManager
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.view.WindowManager.TRANSIT_CLOSE
+import android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS
+import android.view.WindowManager.TRANSIT_NONE
+import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_SLEEP
+import android.view.WindowManager.TRANSIT_TO_BACK
+import android.view.WindowManager.TRANSIT_TO_FRONT
+import android.view.WindowManager.TRANSIT_WAKE
+import android.window.IWindowContainerToken
+import android.window.TransitionInfo
+import android.window.TransitionInfo.Change
+import android.window.WindowContainerToken
+import androidx.test.filters.SmallTest
+import com.android.modules.utils.testing.ExtendedMockitoRule
+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.sysui.ShellInit
+import com.android.wm.shell.transition.TransitionInfoBuilder
+import com.android.wm.shell.transition.Transitions
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.never
+import org.mockito.kotlin.same
+import org.mockito.kotlin.times
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopModeLoggerTransitionObserverTest {
+
+ @JvmField
+ @Rule
+ val extendedMockitoRule = ExtendedMockitoRule.Builder(this)
+ .mockStatic(DesktopModeEventLogger::class.java)
+ .mockStatic(DesktopModeStatus::class.java).build()!!
+
+ @Mock
+ lateinit var testExecutor: ShellExecutor
+ @Mock
+ private lateinit var mockShellInit: ShellInit
+ @Mock
+ private lateinit var transitions: Transitions
+
+ private lateinit var transitionObserver: DesktopModeLoggerTransitionObserver
+ private lateinit var shellInit: ShellInit
+ private lateinit var desktopModeEventLogger: DesktopModeEventLogger
+
+ @Before
+ fun setup() {
+ Mockito.`when`(DesktopModeStatus.isEnabled()).thenReturn(true)
+ shellInit = Mockito.spy(ShellInit(testExecutor))
+ desktopModeEventLogger = mock(DesktopModeEventLogger::class.java)
+
+ transitionObserver = DesktopModeLoggerTransitionObserver(
+ mockShellInit, transitions, desktopModeEventLogger)
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ val initRunnableCaptor = ArgumentCaptor.forClass(
+ Runnable::class.java)
+ verify(mockShellInit).addInitCallback(initRunnableCaptor.capture(),
+ same(transitionObserver))
+ initRunnableCaptor.value.run()
+ } else {
+ transitionObserver.onInit()
+ }
+ }
+
+ @Test
+ fun testRegistersObserverAtInit() {
+ verify(transitions)
+ .registerObserver(same(
+ transitionObserver))
+ }
+
+ @Test
+ fun taskCreated_notFreeformWindow_doesNotLogSessionEnterOrTaskAdded() {
+ val change = createChange(TRANSIT_OPEN, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, never()).logSessionEnter(any(), any())
+ verify(desktopModeEventLogger, never()).logTaskAdded(any(), any())
+ }
+
+ @Test
+ fun taskCreated_FreeformWindowOpen_logSessionEnterAndTaskAdded() {
+ val change = createChange(TRANSIT_OPEN, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+ val sessionId = transitionObserver.getLoggerSessionId()
+
+ assertThat(sessionId).isNotNull()
+ verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+ eq(EnterReason.APP_FREEFORM_INTENT))
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+ }
+
+ @Test
+ fun taskChanged_taskMovedToDesktopByDrag_logSessionEnterAndTaskAdded() {
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ // task change is finalised when drag ends
+ val transitionInfo = TransitionInfoBuilder(
+ Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+ val sessionId = transitionObserver.getLoggerSessionId()
+
+ assertThat(sessionId).isNotNull()
+ verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+ eq(EnterReason.APP_HANDLE_DRAG))
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+ }
+
+ @Test
+ fun taskChanged_taskMovedToDesktopByButtonTap_logSessionEnterAndTaskAdded() {
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(Transitions.TRANSIT_MOVE_TO_DESKTOP, 0)
+ .addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+ val sessionId = transitionObserver.getLoggerSessionId()
+
+ assertThat(sessionId).isNotNull()
+ verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+ eq(EnterReason.APP_HANDLE_MENU_BUTTON))
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+ }
+
+ @Test
+ fun taskChanged_existingFreeformTaskMadeVisible_logSessionEnterAndTaskAdded() {
+ val taskInfo = createTaskInfo(1, WINDOWING_MODE_FREEFORM)
+ taskInfo.isVisibleRequested = true
+ val change = createChange(TRANSIT_CHANGE, taskInfo)
+ val transitionInfo = TransitionInfoBuilder(Transitions.TRANSIT_MOVE_TO_DESKTOP, 0)
+ .addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+ val sessionId = transitionObserver.getLoggerSessionId()
+
+ assertThat(sessionId).isNotNull()
+ verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+ eq(EnterReason.APP_HANDLE_MENU_BUTTON))
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+ }
+
+ @Test
+ fun taskToFront_screenWake_logSessionStartedAndTaskAdded() {
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_WAKE, 0)
+ .addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+ val sessionId = transitionObserver.getLoggerSessionId()
+
+ assertThat(sessionId).isNotNull()
+ verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+ eq(EnterReason.SCREEN_ON))
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+ }
+
+ @Test
+ fun freeformTaskVisible_screenTurnOff_logSessionExitAndTaskRemoved_sessionIdNull() {
+ val sessionId = 1
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_SLEEP).build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+ verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+ eq(ExitReason.SCREEN_OFF))
+ assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ }
+
+ @Test
+ fun freeformTaskVisible_exitDesktopUsingDrag_logSessionExitAndTaskRemoved_sessionIdNull() {
+ val sessionId = 1
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ // window mode changing from FREEFORM to FULLSCREEN
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+ val transitionInfo = TransitionInfoBuilder(Transitions.TRANSIT_EXIT_DESKTOP_MODE)
+ .addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+ verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+ eq(ExitReason.DRAG_TO_EXIT))
+ assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ }
+
+ @Test
+ fun freeformTaskVisible_exitDesktopBySwipeUp_logSessionExitAndTaskRemoved_sessionIdNull() {
+ val sessionId = 1
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ // recents transition
+ val change = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
+ .addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+ verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+ eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
+ assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ }
+
+ @Test
+ fun freeformTaskVisible_taskFinished_logSessionExitAndTaskRemoved_sessionIdNull() {
+ val sessionId = 1
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ // task closing
+ val change = createChange(TRANSIT_CLOSE, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE).addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+ verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+ eq(ExitReason.TASK_FINISHED))
+ assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ }
+
+ @Test
+ fun sessionExitByRecents_cancelledAnimation_sessionRestored() {
+ val sessionId = 1
+ // add a freeform task to an existing session
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ // recents transition sent freeform window to back
+ val change = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val transitionInfo1 =
+ TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS).addChange(change)
+ .build()
+ callOnTransitionReady(transitionInfo1)
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+ verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+ eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
+ assertThat(transitionObserver.getLoggerSessionId()).isNull()
+
+ val transitionInfo2 = TransitionInfoBuilder(TRANSIT_NONE).build()
+ callOnTransitionReady(transitionInfo2)
+
+ verify(desktopModeEventLogger, times(1)).logSessionEnter(any(), any())
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(any(), any())
+ }
+
+ @Test
+ fun sessionAlreadyStarted_newFreeformTaskAdded_logsTaskAdded() {
+ val sessionId = 1
+ // add an existing freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ // new freeform task added
+ val change = createChange(TRANSIT_OPEN, createTaskInfo(2, WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+ verify(desktopModeEventLogger, never()).logSessionEnter(any(), any())
+ }
+
+ @Test
+ fun sessionAlreadyStarted_freeformTaskRemoved_logsTaskRemoved() {
+ val sessionId = 1
+ // add two existing freeform tasks
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(2, WINDOWING_MODE_FREEFORM))
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ // new freeform task added
+ val change = createChange(TRANSIT_CLOSE, createTaskInfo(2, WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+ verify(desktopModeEventLogger, never()).logSessionExit(any(), any())
+ }
+
+ /**
+ * Simulate calling the onTransitionReady() method
+ */
+ private fun callOnTransitionReady(transitionInfo: TransitionInfo) {
+ val transition = mock(IBinder::class.java)
+ val startT = mock(
+ SurfaceControl.Transaction::class.java)
+ val finishT = mock(
+ SurfaceControl.Transaction::class.java)
+
+ transitionObserver.onTransitionReady(transition, transitionInfo, startT, finishT)
+ }
+
+ companion object {
+ fun createTaskInfo(taskId: Int, windowMode: Int): ActivityManager.RunningTaskInfo {
+ val taskInfo = ActivityManager.RunningTaskInfo()
+ taskInfo.taskId = taskId
+ taskInfo.configuration.windowConfiguration.windowingMode = windowMode
+
+ return taskInfo
+ }
+
+ fun createChange(mode: Int, taskInfo: ActivityManager.RunningTaskInfo): Change {
+ val change = Change(
+ WindowContainerToken(mock(
+ IWindowContainerToken::class.java)),
+ mock(SurfaceControl::class.java))
+ change.mode = mode
+ change.taskInfo = taskInfo
+ return change
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
index 9703dce..bd39aa6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
@@ -68,17 +68,17 @@
)
var testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
WINDOWING_MODE_FULLSCREEN, CAPTION_HEIGHT)
- assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
+ assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, 2 * STABLE_INSETS.top))
testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
WINDOWING_MODE_FREEFORM, CAPTION_HEIGHT)
assertThat(testRegion.bounds).isEqualTo(Rect(
DISPLAY_BOUNDS.width() / 2 - fromFreeformWidth / 2,
-50,
DISPLAY_BOUNDS.width() / 2 + fromFreeformWidth / 2,
- 2 * STABLE_INSETS.top))
+ transitionHeight))
testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
WINDOWING_MODE_MULTI_WINDOW, CAPTION_HEIGHT)
- assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
+ assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, 2 * STABLE_INSETS.top))
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 0136751..5df9dd3 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
@@ -87,6 +87,7 @@
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.verify
+import org.mockito.kotlin.times
import org.mockito.Mockito.`when` as whenever
import org.mockito.quality.Strictness
@@ -113,6 +114,7 @@
@Mock lateinit var recentsTransitionHandler: RecentsTransitionHandler
@Mock lateinit var dragAndDropController: DragAndDropController
@Mock lateinit var multiInstanceHelper: MultiInstanceHelper
+ @Mock lateinit var desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var controller: DesktopTasksController
@@ -163,6 +165,7 @@
mToggleResizeDesktopTaskTransitionHandler,
dragToDesktopTransitionHandler,
desktopModeTaskRepository,
+ desktopModeLoggerTransitionObserver,
launchAdjacentController,
recentsTransitionHandler,
multiInstanceHelper,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 3384509..d38fc6c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -129,7 +129,7 @@
}).when(mMockExecutor).execute(any());
mShellInit = spy(new ShellInit(mMockExecutor));
mShellController = spy(new ShellController(mContext, mShellInit, mMockShellCommandHandler,
- mMockExecutor));
+ mMockDisplayInsetsController, mMockExecutor));
mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler,
mShellController, mMockDisplayController, mMockPipAnimationController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 10e9e11..41a4e8d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -58,6 +58,7 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
@@ -96,6 +97,8 @@
private DesktopModeTaskRepository mDesktopModeTaskRepository;
@Mock
private ActivityTaskManager mActivityTaskManager;
+ @Mock
+ private DisplayInsetsController mDisplayInsetsController;
private ShellTaskOrganizer mShellTaskOrganizer;
private RecentTasksController mRecentTasksController;
@@ -110,7 +113,7 @@
when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
mShellInit = spy(new ShellInit(mMainExecutor));
mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
- mMainExecutor));
+ mDisplayInsetsController, mMainExecutor));
mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit,
mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
Optional.of(mDesktopModeTaskRepository), mMainExecutor);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 315d97e..3c387f0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -123,7 +123,7 @@
assumeTrue(ActivityTaskManager.supportsSplitScreenMultiWindow(mContext));
MockitoAnnotations.initMocks(this);
mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
- mMainExecutor));
+ mDisplayInsetsController, mMainExecutor));
mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit,
mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
mRootTDAOrganizer, mDisplayController, mDisplayImeController,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
index 012c4081..ff76a2f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
@@ -40,6 +40,7 @@
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -65,6 +66,7 @@
private @Mock Context mContext;
private @Mock DisplayManager mDisplayManager;
+ private @Mock DisplayInsetsController mDisplayInsetsController;
private @Mock ShellCommandHandler mShellCommandHandler;
private @Mock ShellTaskOrganizer mTaskOrganizer;
private @Mock ShellExecutor mMainExecutor;
@@ -83,7 +85,7 @@
doReturn(super.mContext.getResources()).when(mContext).getResources();
mShellInit = spy(new ShellInit(mMainExecutor));
mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
- mMainExecutor));
+ mDisplayInsetsController, mMainExecutor));
mController = new StartingWindowController(mContext, mShellInit, mShellController,
mTaskOrganizer, mMainExecutor, mTypeAlgorithm, mIconProvider, mTransactionPool);
mShellInit.init();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
index 7c520c3..6292018 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.graphics.Rect;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -35,8 +36,8 @@
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ExternalInterfaceBinder;
-import com.android.wm.shell.common.ShellExecutor;
import org.junit.After;
import org.junit.Before;
@@ -63,12 +64,15 @@
private ShellCommandHandler mShellCommandHandler;
@Mock
private Context mTestUserContext;
+ @Mock
+ private DisplayInsetsController mDisplayInsetsController;
private TestShellExecutor mExecutor;
private ShellController mController;
private TestConfigurationChangeListener mConfigChangeListener;
private TestKeyguardChangeListener mKeyguardChangeListener;
private TestUserChangeListener mUserChangeListener;
+ private TestDisplayImeChangeListener mDisplayImeChangeListener;
@Before
@@ -77,8 +81,10 @@
mKeyguardChangeListener = new TestKeyguardChangeListener();
mConfigChangeListener = new TestConfigurationChangeListener();
mUserChangeListener = new TestUserChangeListener();
+ mDisplayImeChangeListener = new TestDisplayImeChangeListener();
mExecutor = new TestShellExecutor();
- mController = new ShellController(mContext, mShellInit, mShellCommandHandler, mExecutor);
+ mController = new ShellController(mContext, mShellInit, mShellCommandHandler,
+ mDisplayInsetsController, mExecutor);
mController.onConfigurationChanged(getConfigurationCopy());
}
@@ -130,6 +136,45 @@
}
@Test
+ public void testAddDisplayImeChangeListener_ensureCallback() {
+ mController.asShell().addDisplayImeChangeListener(
+ mDisplayImeChangeListener, mExecutor);
+
+ final Rect bounds = new Rect(10, 20, 30, 40);
+ mController.onImeBoundsChanged(bounds);
+ mController.onImeVisibilityChanged(true);
+ mExecutor.flushAll();
+
+ assertTrue(mDisplayImeChangeListener.boundsChanged == 1);
+ assertTrue(bounds.equals(mDisplayImeChangeListener.lastBounds));
+ assertTrue(mDisplayImeChangeListener.visibilityChanged == 1);
+ assertTrue(mDisplayImeChangeListener.lastVisibility);
+ }
+
+ @Test
+ public void testDoubleAddDisplayImeChangeListener_ensureSingleCallback() {
+ mController.asShell().addDisplayImeChangeListener(
+ mDisplayImeChangeListener, mExecutor);
+ mController.asShell().addDisplayImeChangeListener(
+ mDisplayImeChangeListener, mExecutor);
+
+ mController.onImeVisibilityChanged(true);
+ mExecutor.flushAll();
+ assertTrue(mDisplayImeChangeListener.visibilityChanged == 1);
+ }
+
+ @Test
+ public void testAddRemoveDisplayImeChangeListener_ensureNoCallback() {
+ mController.asShell().addDisplayImeChangeListener(
+ mDisplayImeChangeListener, mExecutor);
+ mController.asShell().removeDisplayImeChangeListener(mDisplayImeChangeListener);
+
+ mController.onImeVisibilityChanged(true);
+ mExecutor.flushAll();
+ assertTrue(mDisplayImeChangeListener.visibilityChanged == 0);
+ }
+
+ @Test
public void testAddUserChangeListener_ensureCallback() {
mController.addUserChangeListener(mUserChangeListener);
@@ -457,4 +502,23 @@
lastUserProfiles = profiles;
}
}
+
+ private static class TestDisplayImeChangeListener implements DisplayImeChangeListener {
+ public int boundsChanged = 0;
+ public Rect lastBounds;
+ public int visibilityChanged = 0;
+ public boolean lastVisibility = false;
+
+ @Override
+ public void onImeBoundsChanged(int displayId, Rect bounds) {
+ boundsChanged++;
+ lastBounds = bounds;
+ }
+
+ @Override
+ public void onImeVisibilityChanged(int displayId, boolean isShowing) {
+ visibilityChanged++;
+ lastVisibility = isShowing;
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
index e60be71..e6fabcf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
@@ -189,8 +189,9 @@
DISPLAY_BOUNDS.right - 100,
DISPLAY_BOUNDS.bottom - 100)
- DragPositioningCallbackUtility.onDragEnd(repositionTaskBounds, STARTING_BOUNDS,
- startingPoint, startingPoint.x - 1000, (DISPLAY_BOUNDS.bottom + 1000).toFloat(),
+ DragPositioningCallbackUtility.updateTaskBounds(repositionTaskBounds, STARTING_BOUNDS,
+ startingPoint, startingPoint.x - 1000, (DISPLAY_BOUNDS.bottom + 1000).toFloat())
+ DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(repositionTaskBounds,
validDragArea)
assertThat(repositionTaskBounds.left).isEqualTo(validDragArea.left)
assertThat(repositionTaskBounds.top).isEqualTo(validDragArea.bottom)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index de6903d..ce7b633 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -125,8 +125,7 @@
mockWindowDecoration,
mockDisplayController,
mockDragStartListener,
- mockTransactionFactory,
- DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT
+ mockTransactionFactory
)
}
@@ -576,31 +575,6 @@
})
}
- @Test
- fun testDragResize_drag_setBoundsNotRunIfDragEndsInDisallowedEndArea() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_UNDEFINED, // drag
- STARTING_BOUNDS.right.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- val newX = STARTING_BOUNDS.right.toFloat() + 5
- val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
-
- taskPositioner.onDragPositioningEnd(newX, newY)
-
- verify(mockTransitions, never()).startTransition(
- eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
- }}, eq(taskPositioner))
- }
-
private fun WindowContainerTransaction.Change.ofBounds(bounds: Rect): Boolean {
return ((windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) &&
bounds == configuration.windowConfiguration.bounds
@@ -656,70 +630,6 @@
}
@Test
- fun testDragResize_drag_taskPositionedInStableBounds() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_UNDEFINED, // drag
- STARTING_BOUNDS.left.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- val newX = STARTING_BOUNDS.left.toFloat()
- val newY = STABLE_BOUNDS_LANDSCAPE.top.toFloat() - 5
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
- verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
-
- taskPositioner.onDragPositioningEnd(
- newX,
- newY
- )
- // Verify task's top bound is set to stable bounds top since dragged outside stable bounds
- // but not in disallowed end bounds area.
- verify(mockTransitions).startTransition(
- eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
- change.configuration.windowConfiguration.bounds.top ==
- STABLE_BOUNDS_LANDSCAPE.top
- }}, eq(taskPositioner))
- }
-
- @Test
- fun testDragResize_drag_taskPositionedInValidDragArea() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_UNDEFINED, // drag
- STARTING_BOUNDS.left.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- val newX = VALID_DRAG_AREA.left - 500f
- val newY = VALID_DRAG_AREA.bottom + 500f
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
- verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
-
- taskPositioner.onDragPositioningEnd(
- newX,
- newY
- )
- verify(mockTransitions).startTransition(
- eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
- change.configuration.windowConfiguration.bounds.top ==
- VALID_DRAG_AREA.bottom &&
- change.configuration.windowConfiguration.bounds.left ==
- VALID_DRAG_AREA.left
- }}, eq(taskPositioner))
- }
-
- @Test
fun testDragResize_drag_updatesStableBoundsOnRotate() {
// Test landscape stable bounds
performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index 86253f3..7f6e538 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -138,8 +138,7 @@
mockDisplayController,
mockDragStartListener,
mockTransactionFactory,
- mockTransitions,
- DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT
+ mockTransitions
)
}
@@ -355,68 +354,6 @@
}
@Test
- fun testDragResize_drag_taskPositionedInStableBounds() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_UNDEFINED, // drag
- STARTING_BOUNDS.left.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- val newX = STARTING_BOUNDS.left.toFloat()
- val newY = STABLE_BOUNDS_LANDSCAPE.top.toFloat() - 5
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
- verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
-
- taskPositioner.onDragPositioningEnd(
- newX,
- newY
- )
- // Verify task's top bound is set to stable bounds top since dragged outside stable bounds
- // but not in disallowed end bounds area.
- verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
- change.configuration.windowConfiguration.bounds.top ==
- STABLE_BOUNDS_LANDSCAPE.top }},
- eq(taskPositioner))
- }
-
- @Test
- fun testDragResize_drag_taskPositionedInValidDragArea() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_UNDEFINED, // drag
- STARTING_BOUNDS.left.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- val newX = VALID_DRAG_AREA.left - 500f
- val newY = VALID_DRAG_AREA.bottom + 500f
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
- verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
-
- taskPositioner.onDragPositioningEnd(
- newX,
- newY
- )
- verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
- change.configuration.windowConfiguration.bounds.top ==
- VALID_DRAG_AREA.bottom &&
- change.configuration.windowConfiguration.bounds.left ==
- VALID_DRAG_AREA.left }},
- eq(taskPositioner))
- }
-
- @Test
fun testDragResize_drag_updatesStableBoundsOnRotate() {
// Test landscape stable bounds
performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index 156be38..f33bcb7 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -11,7 +11,7 @@
name: "location_bypass"
namespace: "location"
description: "Enable location bypass appops behavior"
- bug: "301150056"
+ bug: "329151785"
}
flag {
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 6cf9c6f..bf39425 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -109,5 +109,5 @@
name: "enable_null_session_in_media_browser_service"
namespace: "media_solutions"
description: "Enables apps owning a MediaBrowserService to disconnect all connected browsers."
- bug: "263520343"
+ bug: "185136506"
}
diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java
index 76664a6..ddcb25b 100644
--- a/media/java/android/media/tv/ad/TvAdManager.java
+++ b/media/java/android/media/tv/ad/TvAdManager.java
@@ -61,7 +61,6 @@
@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
@SystemService(Context.TV_AD_SERVICE)
public final class TvAdManager {
- // TODO: implement more methods and unhide APIs.
private static final String TAG = "TvAdManager";
/**
diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java
index d204908..dd2a534 100644
--- a/media/java/android/media/tv/ad/TvAdView.java
+++ b/media/java/android/media/tv/ad/TvAdView.java
@@ -393,16 +393,12 @@
}
/**
- * Sets a listener to be invoked when an input event is not handled
- * by the TV AD service.
+ * Sets a listener to be invoked when an input event is not handled by the TV AD service.
*
* @param listener The callback to be invoked when the unhandled input event is received.
*/
- public void setOnUnhandledInputEventListener(
- @NonNull @CallbackExecutor Executor executor,
- @NonNull OnUnhandledInputEventListener listener) {
+ public void setOnUnhandledInputEventListener(@NonNull OnUnhandledInputEventListener listener) {
mOnUnhandledInputEventListener = listener;
- // TODO: handle CallbackExecutor
}
/**
@@ -441,6 +437,9 @@
/**
* Prepares the AD service of corresponding {@link TvAdService}.
*
+ * <p>This should be called before calling {@link #startAdService()}. Otherwise,
+ * {@link #startAdService()} is a no-op.
+ *
* @param serviceId the AD service ID, which can be found in TvAdServiceInfo#getId().
*/
public void prepareAdService(@NonNull String serviceId, @NonNull String type) {
@@ -455,6 +454,9 @@
/**
* Starts the AD service.
+ *
+ * <p>This should be called after calling {@link #prepareAdService(String, String)}. Otherwise,
+ * it's a no-op.
*/
public void startAdService() {
if (DEBUG) {
@@ -467,6 +469,8 @@
/**
* Stops the AD service.
+ *
+ * <p>It's a no-op if the service is not started.
*/
public void stopAdService() {
if (DEBUG) {
diff --git a/nfc/Android.bp b/nfc/Android.bp
index 7dd16ba..7698e2b 100644
--- a/nfc/Android.bp
+++ b/nfc/Android.bp
@@ -76,6 +76,9 @@
"//apex_available:platform",
"com.android.nfcservices",
],
+ aconfig_declarations: [
+ "android.nfc.flags-aconfig",
+ ],
}
filegroup {
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java
index 4500a22..5dcfd3b 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java
@@ -40,7 +40,7 @@
*
* This can be called using the JavaScript below:
* <script type="text/javascript">
- * function getRequestedCapability(duration) {
+ * function getRequestedCapability() {
* DataBoostWebServiceFlow.getRequestedCapability();
* }
* </script>
@@ -57,6 +57,25 @@
*
* This can be called using the JavaScript below:
* <script type="text/javascript">
+ * function notifyPurchaseSuccessful(duration_ms_long = 0) {
+ * DataBoostWebServiceFlow.notifyPurchaseSuccessful(duration_ms_long);
+ * }
+ * </script>
+ *
+ * @param duration The duration for which the premium capability is purchased in milliseconds.
+ * NOTE: The duration parameter is not used.
+ */
+ @JavascriptInterface
+ public void notifyPurchaseSuccessful(long duration) {
+ mActivity.onPurchaseSuccessful();
+ }
+
+ /**
+ * Interface method allowing the carrier website to notify the slice purchase application of
+ * a successful premium capability purchase.
+ *
+ * This can be called using the JavaScript below:
+ * <script type="text/javascript">
* function notifyPurchaseSuccessful() {
* DataBoostWebServiceFlow.notifyPurchaseSuccessful();
* }
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 6019aa8..42d0cc4 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -52,12 +52,21 @@
<!-- Confirmation for associating an application with a companion device of APP_STREAMING profile (type) [CHAR LIMIT=NONE] -->
<string name="title_app_streaming">Allow <strong><xliff:g id="app_name" example="Exo">%1$s</xliff:g></strong> to access this information from your phone</string>
+ <!-- Confirmation for associating an application with a companion device of APP_STREAMING profile (type) with mirroring enabled [CHAR LIMIT=NONE] -->
+ <string name="title_app_streaming_with_mirroring">Allow <strong><xliff:g id="app_name" example="Exo">%1$s</xliff:g></strong> to stream your phone\u2019s apps?</string>
+
+ <!-- Summary for associating an application with a companion device of APP_STREAMING profile [CHAR LIMIT=NONE] -->
+ <string name="summary_app_streaming">%1$s will have access to anything that’s visible or played on the phone, including audio, photos, passwords, and messages.<br/><br/>%1$s will be able to stream apps until you remove access to this permission.</string>
+
<!-- Title of the helper dialog for APP_STREAMING profile [CHAR LIMIT=30]. -->
<string name="helper_title_app_streaming">Cross-device services</string>
<!-- Description of the helper dialog for APP_STREAMING profile. [CHAR LIMIT=NONE] -->
<string name="helper_summary_app_streaming"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="display_name" example="Chromebook">%2$s</xliff:g> to stream apps between your devices</string>
+ <!-- Description of the helper dialog for APP_STREAMING profile with mirroring enabled. [CHAR LIMIT=NONE] -->
+ <string name="helper_summary_app_streaming_with_mirroring"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="display_name" example="Chromebook">%2$s</xliff:g> to display and stream apps between your devices</string>
+
<!-- ================= DEVICE_PROFILE_AUTOMOTIVE_PROJECTION ================= -->
<!-- Confirmation for associating an application with a companion device of AUTOMOTIVE_PROJECTION profile (type) [CHAR LIMIT=NONE] -->
@@ -85,6 +94,12 @@
<!-- Confirmation for associating an application with a companion device of NEARBY_DEVICE_STREAMING profile (type) [CHAR LIMIT=NONE] -->
<string name="title_nearby_device_streaming">Allow <strong><xliff:g id="device_name" example="NearbyStreamer">%1$s</xliff:g></strong> to take this action?</string>
+ <!-- Confirmation for associating an application with a companion device of NEARBY_DEVICE_STREAMING profile (type) with mirroring enabled [CHAR LIMIT=NONE] -->
+ <string name="title_nearby_device_streaming_with_mirroring">Allow <strong><xliff:g id="device_name" example="NearbyStreamer">%1$s</xliff:g></strong> to stream your phone\u2019s apps and system features?</string>
+
+ <!-- Summary for associating an application with a companion device of NEARBY_DEVICE_STREAMING profile [CHAR LIMIT=NONE] -->
+ <string name="summary_nearby_device_streaming">%1$s will have access to anything that’s visible or played on your phone, including audio, photos, payment info, passwords, and messages.<br/><br/>%1$s will be able to stream apps and system features until you remove access to this permission.</string>
+
<!-- Description of the helper dialog for NEARBY_DEVICE_STREAMING profile. [CHAR LIMIT=NONE] -->
<string name="helper_summary_nearby_device_streaming"><xliff:g id="app_name" example="NearbyStreamerApp">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_name" example="NearbyDevice">%2$s</xliff:g> to stream apps and other system features to nearby devices</string>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 4c1f631..1231b63 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -179,7 +179,7 @@
// onActivityResult() after the association is created.
private @Nullable DeviceFilterPair<?> mSelectedDevice;
- private LinearLayoutManager mPermissionsLayoutManager = new LinearLayoutManager(this);
+ private final LinearLayoutManager mPermissionsLayoutManager = new LinearLayoutManager(this);
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -484,10 +484,18 @@
}
title = getHtmlFromResources(this, PROFILE_TITLES.get(deviceProfile), deviceName);
+
+ if (PROFILE_SUMMARIES.containsKey(deviceProfile)) {
+ final int summaryResourceId = PROFILE_SUMMARIES.get(deviceProfile);
+ final Spanned summary = getHtmlFromResources(this, summaryResourceId,
+ deviceName);
+ mSummary.setText(summary);
+ } else {
+ mSummary.setVisibility(View.GONE);
+ }
+
setupPermissionList(deviceProfile);
- // Summary is not needed for selfManaged dialog.
- mSummary.setVisibility(View.GONE);
mTitle.setText(title);
mVendorHeaderName.setText(vendorName);
mVendorHeader.setVisibility(View.VISIBLE);
@@ -692,6 +700,11 @@
private void setupPermissionList(String deviceProfile) {
final List<Integer> permissionTypes = new ArrayList<>(
PROFILE_PERMISSIONS.get(deviceProfile));
+ if (permissionTypes.isEmpty()) {
+ // Nothing to do if there are no permission types.
+ return;
+ }
+
mPermissionListAdapter.setPermissionType(permissionTypes);
mPermissionListRecyclerView.setAdapter(mPermissionListAdapter);
mPermissionListRecyclerView.setLayoutManager(mPermissionsLayoutManager);
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
index 23a11d6..dc68bcc 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
@@ -27,11 +27,13 @@
import static java.util.Collections.unmodifiableMap;
import static java.util.Collections.unmodifiableSet;
+import android.companion.virtual.flags.Flags;
import android.os.Build;
import android.util.ArrayMap;
import android.util.ArraySet;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -122,10 +124,19 @@
static final Map<String, Integer> PROFILE_TITLES;
static {
final Map<String, Integer> map = new ArrayMap<>();
- map.put(DEVICE_PROFILE_APP_STREAMING, R.string.title_app_streaming);
+ if (Flags.interactiveScreenMirror()) {
+ map.put(DEVICE_PROFILE_APP_STREAMING, R.string.title_app_streaming_with_mirroring);
+ } else {
+ map.put(DEVICE_PROFILE_APP_STREAMING, R.string.title_app_streaming);
+ }
map.put(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, R.string.title_automotive_projection);
map.put(DEVICE_PROFILE_COMPUTER, R.string.title_computer);
- map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, R.string.title_nearby_device_streaming);
+ if (Flags.interactiveScreenMirror()) {
+ map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
+ R.string.title_nearby_device_streaming_with_mirroring);
+ } else {
+ map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, R.string.title_nearby_device_streaming);
+ }
map.put(DEVICE_PROFILE_WATCH, R.string.confirmation_title);
map.put(DEVICE_PROFILE_GLASSES, R.string.confirmation_title_glasses);
map.put(null, R.string.confirmation_title);
@@ -138,6 +149,11 @@
final Map<String, Integer> map = new ArrayMap<>();
map.put(DEVICE_PROFILE_WATCH, R.string.summary_watch);
map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses);
+ if (Flags.interactiveScreenMirror()) {
+ map.put(DEVICE_PROFILE_APP_STREAMING, R.string.summary_app_streaming);
+ map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
+ R.string.summary_nearby_device_streaming);
+ }
map.put(null, R.string.summary_generic);
PROFILE_SUMMARIES = unmodifiableMap(map);
@@ -146,11 +162,16 @@
static final Map<String, List<Integer>> PROFILE_PERMISSIONS;
static {
final Map<String, List<Integer>> map = new ArrayMap<>();
- map.put(DEVICE_PROFILE_APP_STREAMING, Arrays.asList(PERMISSION_APP_STREAMING));
map.put(DEVICE_PROFILE_COMPUTER, Arrays.asList(
PERMISSION_NOTIFICATION_LISTENER_ACCESS, PERMISSION_STORAGE));
- map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
- Arrays.asList(PERMISSION_NEARBY_DEVICE_STREAMING));
+ if (Flags.interactiveScreenMirror()) {
+ map.put(DEVICE_PROFILE_APP_STREAMING, Collections.emptyList());
+ map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, Collections.emptyList());
+ } else {
+ map.put(DEVICE_PROFILE_APP_STREAMING, Arrays.asList(PERMISSION_APP_STREAMING));
+ map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
+ Arrays.asList(PERMISSION_NEARBY_DEVICE_STREAMING));
+ }
if (Build.VERSION.SDK_INT > UPSIDE_DOWN_CAKE) {
map.put(DEVICE_PROFILE_WATCH, Arrays.asList(PERMISSION_NOTIFICATIONS, PERMISSION_PHONE,
PERMISSION_CALL_LOGS, PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_CALENDAR,
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
index 8f32dbb..fe0e021 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
@@ -26,6 +26,7 @@
import android.annotation.Nullable;
import android.companion.AssociationRequest;
+import android.companion.virtual.flags.Flags;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
@@ -129,7 +130,9 @@
case DEVICE_PROFILE_APP_STREAMING:
title = getHtmlFromResources(getContext(), R.string.helper_title_app_streaming);
summary = getHtmlFromResources(
- getContext(), R.string.helper_summary_app_streaming, title, displayName);
+ getContext(), Flags.interactiveScreenMirror()
+ ? R.string.helper_summary_app_streaming_with_mirroring
+ : R.string.helper_summary_app_streaming, title, displayName);
break;
case DEVICE_PROFILE_COMPUTER:
diff --git a/packages/CrashRecovery/OWNERS b/packages/CrashRecovery/OWNERS
index daa0211..8337fd2 100644
--- a/packages/CrashRecovery/OWNERS
+++ b/packages/CrashRecovery/OWNERS
@@ -1,3 +1 @@
-ancr@google.com
-harshitmahajan@google.com
-robertogil@google.com
+include /services/core/java/com/android/server/crashrecovery/OWNERS
\ No newline at end of file
diff --git a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
index 5d71b7d..37b5d40 100644
--- a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
@@ -38,15 +38,15 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
-import android.util.BackgroundThread;
-import android.util.LongArrayQueue;
import android.util.Slog;
import android.util.Xml;
+import android.utils.BackgroundThread;
+import android.utils.LongArrayQueue;
+import android.utils.XmlUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
index 9217e70..d0fee44 100644
--- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
@@ -29,7 +29,6 @@
import android.content.pm.VersionedPackage;
import android.os.Build;
import android.os.Environment;
-import android.os.FileUtils;
import android.os.PowerManager;
import android.os.RecoverySystem;
import android.os.SystemClock;
@@ -42,10 +41,11 @@
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
+import android.utils.ArrayUtils;
+import android.utils.FileUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
import com.android.server.PackageWatchdog.FailureReasons;
import com.android.server.PackageWatchdog.PackageHealthObserver;
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
@@ -707,7 +707,7 @@
if (pm.getModuleInfo(packageName, 0) != null) {
return true;
}
- } catch (PackageManager.NameNotFoundException ignore) {
+ } catch (PackageManager.NameNotFoundException | IllegalStateException ignore) {
}
return isPersistentSystemApp(packageName);
diff --git a/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java b/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java
new file mode 100644
index 0000000..fa4d6af
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.utils;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.io.File;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Copied over from frameworks/base/core/java/com/android/internal/util/ArrayUtils.java
+ *
+ * @hide
+ */
+public class ArrayUtils {
+ private ArrayUtils() { /* cannot be instantiated */ }
+ public static final File[] EMPTY_FILE = new File[0];
+
+
+ /**
+ * Return first index of {@code value} in {@code array}, or {@code -1} if
+ * not found.
+ */
+ public static <T> int indexOf(@Nullable T[] array, T value) {
+ if (array == null) return -1;
+ for (int i = 0; i < array.length; i++) {
+ if (Objects.equals(array[i], value)) return i;
+ }
+ return -1;
+ }
+
+ /** @hide */
+ public static @NonNull File[] defeatNullable(@Nullable File[] val) {
+ return (val != null) ? val : EMPTY_FILE;
+ }
+
+ /**
+ * Checks if given array is null or has zero elements.
+ */
+ public static boolean isEmpty(@Nullable int[] array) {
+ return array == null || array.length == 0;
+ }
+
+ /**
+ * True if the byte array is null or has length 0.
+ */
+ public static boolean isEmpty(@Nullable byte[] array) {
+ return array == null || array.length == 0;
+ }
+
+ /**
+ * Converts from List of bytes to byte array
+ * @param list
+ * @return byte[]
+ */
+ public static byte[] toPrimitive(List<byte[]> list) {
+ if (list.size() == 0) {
+ return new byte[0];
+ }
+ int byteLen = list.get(0).length;
+ byte[] array = new byte[list.size() * byteLen];
+ for (int i = 0; i < list.size(); i++) {
+ for (int j = 0; j < list.get(i).length; j++) {
+ array[i * byteLen + j] = list.get(i)[j];
+ }
+ }
+ return array;
+ }
+
+ /**
+ * Adds value to given array if not already present, providing set-like
+ * behavior.
+ */
+ public static @NonNull int[] appendInt(@Nullable int[] cur, int val) {
+ return appendInt(cur, val, false);
+ }
+
+ /**
+ * Adds value to given array.
+ */
+ public static @NonNull int[] appendInt(@Nullable int[] cur, int val,
+ boolean allowDuplicates) {
+ if (cur == null) {
+ return new int[] { val };
+ }
+ final int n = cur.length;
+ if (!allowDuplicates) {
+ for (int i = 0; i < n; i++) {
+ if (cur[i] == val) {
+ return cur;
+ }
+ }
+ }
+ int[] ret = new int[n + 1];
+ System.arraycopy(cur, 0, ret, 0, n);
+ ret[n] = val;
+ return ret;
+ }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java b/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java
similarity index 99%
rename from packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java
rename to packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java
index a6ae68f..afcf689 100644
--- a/packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java
+++ b/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.util;
+package android.utils;
import android.annotation.NonNull;
import android.os.Handler;
diff --git a/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java b/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java
new file mode 100644
index 0000000..e4923bf
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java
@@ -0,0 +1,128 @@
+/*
+ * 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.utils;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Bits and pieces copied from hidden API of android.os.FileUtils.
+ *
+ * @hide
+ */
+public class FileUtils {
+ /**
+ * Read a text file into a String, optionally limiting the length.
+ *
+ * @param file to read (will not seek, so things like /proc files are OK)
+ * @param max length (positive for head, negative of tail, 0 for no limit)
+ * @param ellipsis to add of the file was truncated (can be null)
+ * @return the contents of the file, possibly truncated
+ * @throws IOException if something goes wrong reading the file
+ * @hide
+ */
+ public static @Nullable String readTextFile(@Nullable File file, @Nullable int max,
+ @Nullable String ellipsis) throws IOException {
+ InputStream input = new FileInputStream(file);
+ // wrapping a BufferedInputStream around it because when reading /proc with unbuffered
+ // input stream, bytes read not equal to buffer size is not necessarily the correct
+ // indication for EOF; but it is true for BufferedInputStream due to its implementation.
+ BufferedInputStream bis = new BufferedInputStream(input);
+ try {
+ long size = file.length();
+ if (max > 0 || (size > 0 && max == 0)) { // "head" mode: read the first N bytes
+ if (size > 0 && (max == 0 || size < max)) max = (int) size;
+ byte[] data = new byte[max + 1];
+ int length = bis.read(data);
+ if (length <= 0) return "";
+ if (length <= max) return new String(data, 0, length);
+ if (ellipsis == null) return new String(data, 0, max);
+ return new String(data, 0, max) + ellipsis;
+ } else if (max < 0) { // "tail" mode: keep the last N
+ int len;
+ boolean rolled = false;
+ byte[] last = null;
+ byte[] data = null;
+ do {
+ if (last != null) rolled = true;
+ byte[] tmp = last;
+ last = data;
+ data = tmp;
+ if (data == null) data = new byte[-max];
+ len = bis.read(data);
+ } while (len == data.length);
+
+ if (last == null && len <= 0) return "";
+ if (last == null) return new String(data, 0, len);
+ if (len > 0) {
+ rolled = true;
+ System.arraycopy(last, len, last, 0, last.length - len);
+ System.arraycopy(data, 0, last, last.length - len, len);
+ }
+ if (ellipsis == null || !rolled) return new String(last);
+ return ellipsis + new String(last);
+ } else { // "cat" mode: size unknown, read it all in streaming fashion
+ ByteArrayOutputStream contents = new ByteArrayOutputStream();
+ int len;
+ byte[] data = new byte[1024];
+ do {
+ len = bis.read(data);
+ if (len > 0) contents.write(data, 0, len);
+ } while (len == data.length);
+ return contents.toString();
+ }
+ } finally {
+ bis.close();
+ input.close();
+ }
+ }
+
+ /**
+ * Perform an fsync on the given FileOutputStream. The stream at this
+ * point must be flushed but not yet closed.
+ *
+ * @hide
+ */
+ public static boolean sync(FileOutputStream stream) {
+ try {
+ if (stream != null) {
+ stream.getFD().sync();
+ }
+ return true;
+ } catch (IOException e) {
+ }
+ return false;
+ }
+
+ /**
+ * List the files in the directory or return empty file.
+ *
+ * @hide
+ */
+ public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) {
+ return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles())
+ : ArrayUtils.EMPTY_FILE;
+ }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java b/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java
similarity index 98%
rename from packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java
rename to packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java
index 948ebcca..fdb15e2 100644
--- a/packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java
+++ b/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.util;
+package android.utils;
import android.annotation.NonNull;
import android.os.Handler;
diff --git a/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java b/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java
new file mode 100644
index 0000000..5cdc253
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java
@@ -0,0 +1,188 @@
+/*
+ * 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.utils;
+
+import libcore.util.EmptyArray;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Copied from frameworks/base/core/java/android/util/LongArrayQueue.java
+ *
+ * @hide
+ */
+public class LongArrayQueue {
+
+ private long[] mValues;
+ private int mSize;
+ private int mHead;
+ private int mTail;
+
+ private long[] newUnpaddedLongArray(int num) {
+ return new long[num];
+ }
+ /**
+ * Initializes a queue with the given starting capacity.
+ *
+ * @param initialCapacity the capacity.
+ */
+ public LongArrayQueue(int initialCapacity) {
+ if (initialCapacity == 0) {
+ mValues = EmptyArray.LONG;
+ } else {
+ mValues = newUnpaddedLongArray(initialCapacity);
+ }
+ mSize = 0;
+ mHead = mTail = 0;
+ }
+
+ /**
+ * Initializes a queue with default starting capacity.
+ */
+ public LongArrayQueue() {
+ this(16);
+ }
+
+ /** @hide */
+ public static int growSize(int currentSize) {
+ return currentSize <= 4 ? 8 : currentSize * 2;
+ }
+
+ private void grow() {
+ if (mSize < mValues.length) {
+ throw new IllegalStateException("Queue not full yet!");
+ }
+ final int newSize = growSize(mSize);
+ final long[] newArray = newUnpaddedLongArray(newSize);
+ final int r = mValues.length - mHead; // Number of elements on and to the right of head.
+ System.arraycopy(mValues, mHead, newArray, 0, r);
+ System.arraycopy(mValues, 0, newArray, r, mHead);
+ mValues = newArray;
+ mHead = 0;
+ mTail = mSize;
+ }
+
+ /**
+ * Returns the number of elements in the queue.
+ */
+ public int size() {
+ return mSize;
+ }
+
+ /**
+ * Removes all elements from this queue.
+ */
+ public void clear() {
+ mSize = 0;
+ mHead = mTail = 0;
+ }
+
+ /**
+ * Adds a value to the tail of the queue.
+ *
+ * @param value the value to be added.
+ */
+ public void addLast(long value) {
+ if (mSize == mValues.length) {
+ grow();
+ }
+ mValues[mTail] = value;
+ mTail = (mTail + 1) % mValues.length;
+ mSize++;
+ }
+
+ /**
+ * Removes an element from the head of the queue.
+ *
+ * @return the element at the head of the queue.
+ * @throws NoSuchElementException if the queue is empty.
+ */
+ public long removeFirst() {
+ if (mSize == 0) {
+ throw new NoSuchElementException("Queue is empty!");
+ }
+ final long ret = mValues[mHead];
+ mHead = (mHead + 1) % mValues.length;
+ mSize--;
+ return ret;
+ }
+
+ /**
+ * Returns the element at the given position from the head of the queue, where 0 represents the
+ * head of the queue.
+ *
+ * @param position the position from the head of the queue.
+ * @return the element found at the given position.
+ * @throws IndexOutOfBoundsException if {@code position} < {@code 0} or
+ * {@code position} >= {@link #size()}
+ */
+ public long get(int position) {
+ if (position < 0 || position >= mSize) {
+ throw new IndexOutOfBoundsException("Index " + position
+ + " not valid for a queue of size " + mSize);
+ }
+ final int index = (mHead + position) % mValues.length;
+ return mValues[index];
+ }
+
+ /**
+ * Returns the element at the head of the queue, without removing it.
+ *
+ * @return the element at the head of the queue.
+ * @throws NoSuchElementException if the queue is empty
+ */
+ public long peekFirst() {
+ if (mSize == 0) {
+ throw new NoSuchElementException("Queue is empty!");
+ }
+ return mValues[mHead];
+ }
+
+ /**
+ * Returns the element at the tail of the queue.
+ *
+ * @return the element at the tail of the queue.
+ * @throws NoSuchElementException if the queue is empty.
+ */
+ public long peekLast() {
+ if (mSize == 0) {
+ throw new NoSuchElementException("Queue is empty!");
+ }
+ final int index = (mTail == 0) ? mValues.length - 1 : mTail - 1;
+ return mValues[index];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ if (mSize <= 0) {
+ return "{}";
+ }
+
+ final StringBuilder buffer = new StringBuilder(mSize * 64);
+ buffer.append('{');
+ buffer.append(get(0));
+ for (int i = 1; i < mSize; i++) {
+ buffer.append(", ");
+ buffer.append(get(i));
+ }
+ buffer.append('}');
+ return buffer.toString();
+ }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java b/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java
new file mode 100644
index 0000000..dbbef61
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java
@@ -0,0 +1,118 @@
+/*
+ * 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.utils;
+
+import android.annotation.NonNull;
+import android.system.ErrnoException;
+import android.system.Os;
+
+import com.android.modules.utils.TypedXmlPullParser;
+
+import libcore.util.XmlObjectFactory;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Copied over partly from frameworks/base/core/java/com/android/internal/util/XmlUtils.java
+ *
+ * @hide
+ */
+public class XmlUtils {
+
+ private static final String STRING_ARRAY_SEPARATOR = ":";
+
+ /** @hide */
+ public static final void beginDocument(XmlPullParser parser, String firstElementName)
+ throws XmlPullParserException, IOException {
+ int type;
+ while ((type = parser.next()) != parser.START_TAG
+ && type != parser.END_DOCUMENT) {
+ // Do nothing
+ }
+
+ if (type != parser.START_TAG) {
+ throw new XmlPullParserException("No start tag found");
+ }
+
+ if (!parser.getName().equals(firstElementName)) {
+ throw new XmlPullParserException("Unexpected start tag: found " + parser.getName()
+ + ", expected " + firstElementName);
+ }
+ }
+
+ /** @hide */
+ public static boolean nextElementWithin(XmlPullParser parser, int outerDepth)
+ throws IOException, XmlPullParserException {
+ for (;;) {
+ int type = parser.next();
+ if (type == XmlPullParser.END_DOCUMENT
+ || (type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth)) {
+ return false;
+ }
+ if (type == XmlPullParser.START_TAG
+ && parser.getDepth() == outerDepth + 1) {
+ return true;
+ }
+ }
+ }
+
+ private static XmlPullParser newPullParser() {
+ try {
+ XmlPullParser parser = XmlObjectFactory.newXmlPullParser();
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true);
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+ return parser;
+ } catch (XmlPullParserException e) {
+ throw new AssertionError();
+ }
+ }
+
+ /** @hide */
+ public static @NonNull TypedXmlPullParser resolvePullParser(@NonNull InputStream in)
+ throws IOException {
+ final byte[] magic = new byte[4];
+ if (in instanceof FileInputStream) {
+ try {
+ Os.pread(((FileInputStream) in).getFD(), magic, 0, magic.length, 0);
+ } catch (ErrnoException e) {
+ throw e.rethrowAsIOException();
+ }
+ } else {
+ if (!in.markSupported()) {
+ in = new BufferedInputStream(in);
+ }
+ in.mark(8);
+ in.read(magic);
+ in.reset();
+ }
+
+ final TypedXmlPullParser xml;
+ xml = (TypedXmlPullParser) newPullParser();
+ try {
+ xml.setInput(in, "UTF_8");
+ } catch (XmlPullParserException e) {
+ throw new IOException(e);
+ }
+ return xml;
+ }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 4f2fa79..6ba684d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -574,7 +574,7 @@
) {
viewNode.autofillId?.let {
val domain = viewNode.webDomain
- val request = viewNode.credentialManagerRequest
+ val request = viewNode.pendingCredentialRequest
if (domain != null && request != null) {
responseClientState.putBoolean(
WEBVIEW_REQUESTED_CREDENTIAL_KEY, true)
@@ -604,8 +604,8 @@
sessionId: Int
): MutableList<CredentialOption> {
val credentialOptions: MutableList<CredentialOption> = mutableListOf()
- if (Flags.autofillCredmanDevIntegration() && viewNode.credentialManagerRequest != null) {
- viewNode.credentialManagerRequest
+ if (Flags.autofillCredmanDevIntegration() && viewNode.pendingCredentialRequest != null) {
+ viewNode.pendingCredentialRequest
?.getCredentialOptions()
?.forEach { credentialOption ->
credentialOption.candidateQueryData
diff --git a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenImagePathManager.kt b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenPathManager.kt
similarity index 77%
rename from packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenImagePathManager.kt
rename to packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenPathManager.kt
index 6aef24d..9cfdffd 100644
--- a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenImagePathManager.kt
+++ b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenPathManager.kt
@@ -18,34 +18,34 @@
import android.os.Build
import androidx.test.platform.app.InstrumentationRegistry
-import platform.test.screenshot.GoldenImagePathManager
+import platform.test.screenshot.GoldenPathManager
import platform.test.screenshot.PathConfig
/** The assets path to be used by all CredentialManager screenshot tests. */
private const val ASSETS_PREFIX = "frameworks/base/packages/CredentialManager"
private const val ASSETS_PATH = "${ASSETS_PREFIX}/tests/robotests/screenshot/customization/assets"
-private const val ASSETS_PATH_ROBO =
- "${ASSETS_PREFIX}/tests/robotests/customization/assets"
+private const val ASSETS_PATH_ROBO = "${ASSETS_PREFIX}/tests/robotests/customization/assets"
private val isRobolectric = Build.FINGERPRINT.contains("robolectric")
-class CredentialManagerGoldenImagePathManager(
- pathConfig: PathConfig,
- assetsPathRelativeToBuildRoot: String = if (isRobolectric) ASSETS_PATH_ROBO else ASSETS_PATH
-) : GoldenImagePathManager(
+class CredentialManagerGoldenPathManager(
+ pathConfig: PathConfig,
+ assetsPathRelativeToBuildRoot: String = if (isRobolectric) ASSETS_PATH_ROBO else ASSETS_PATH
+) :
+ GoldenPathManager(
appContext = InstrumentationRegistry.getInstrumentation().context,
assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
deviceLocalPath =
- InstrumentationRegistry.getInstrumentation()
+ InstrumentationRegistry.getInstrumentation()
.targetContext
.filesDir
.absolutePath
.toString() + "/credman_screenshots",
pathConfig = pathConfig,
-) {
+ ) {
override fun toString(): String {
// This string is appended to all actual/expected screenshots on the device, so make sure
// it is a static value.
- return "CredentialManagerGoldenImagePathManager"
+ return "CredentialManagerGoldenPathManager"
}
-}
\ No newline at end of file
+}
diff --git a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
index 28d83ee..b843213 100644
--- a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
+++ b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
@@ -60,7 +60,7 @@
@get:Rule
val screenshotRule = ComposeScreenshotTestRule(
emulationSpec,
- CredentialManagerGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
+ CredentialManagerGoldenPathManager(getEmulatedDevicePathConfig(emulationSpec))
)
@get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule()
diff --git a/packages/CredentialManager/wear/res/values/strings.xml b/packages/CredentialManager/wear/res/values/strings.xml
index 9480e64..ee8bb78 100644
--- a/packages/CredentialManager/wear/res/values/strings.xml
+++ b/packages/CredentialManager/wear/res/values/strings.xml
@@ -21,9 +21,6 @@
<!-- Title of a screen prompting if the user would like to use their saved passkey.
[CHAR LIMIT=80] -->
<string name="use_passkey_title">Use passkey?</string>
- <!-- Title of a screen prompting if the user would like to use their saved passkey.
-[CHAR LIMIT=80] -->
- <string name="use_sign_in_with_provider_title">Use your sign in for %1$s</string>
<!-- Title of a screen prompting if the user would like to sign in with provider
[CHAR LIMIT=80] -->
<string name="use_password_title">Use password?</string>
@@ -35,6 +32,8 @@
<string name="dialog_sign_in_options_button">Sign-in Options</string>
<!-- Title for multiple credentials folded screen. [CHAR LIMIT=NONE] -->
<string name="sign_in_options_title">Sign-in Options</string>
+ <!-- Provider settings list title. [CHAR LIMIT=NONE] -->
+ <string name="provider_list_title">Manage sign-ins</string>
<!-- Title for multiple credentials screen. [CHAR LIMIT=NONE] -->
<string name="choose_sign_in_title">Choose a sign in</string>
<!-- Title for multiple credentials screen with only passkeys. [CHAR LIMIT=NONE] -->
diff --git a/packages/CredentialManager/wear/robotests/Android.bp b/packages/CredentialManager/wear/robotests/Android.bp
new file mode 100644
index 0000000..c0a1822
--- /dev/null
+++ b/packages/CredentialManager/wear/robotests/Android.bp
@@ -0,0 +1,28 @@
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_robolectric_test {
+ name: "CredentialSelectorTests",
+ srcs: [
+ "src/**/*.kt",
+ ],
+ // Include test libraries.
+ instrumentation_for: "ClockworkCredentialManager",
+ libs: [
+ "androidx.test.runner",
+ "androidx.test.ext.junit",
+ "kotlinx_coroutines_android",
+ "kotlinx_coroutines",
+ "kotlinx-coroutines-core",
+ "kotlinx_coroutines_test",
+ "mockito-robolectric-prebuilt",
+ "mockito-kotlin2",
+ "CredentialManagerShared",
+ "ClockworkCredentialManager",
+ "framework_graphics_flags_java_lib",
+ ],
+ java_resource_dirs: ["config"],
+ upstream: true,
+}
diff --git a/packages/CredentialManager/wear/robotests/config/robolectric.properties b/packages/CredentialManager/wear/robotests/config/robolectric.properties
new file mode 100644
index 0000000..140e42b
--- /dev/null
+++ b/packages/CredentialManager/wear/robotests/config/robolectric.properties
@@ -0,0 +1,16 @@
+# 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.
+#
+sdk=NEWEST_SDK
+
diff --git a/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt
new file mode 100644
index 0000000..3422d3d
--- /dev/null
+++ b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt
@@ -0,0 +1,214 @@
+/*
+ * 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.credentialmanager
+
+import java.time.Instant
+import android.graphics.drawable.Drawable
+import com.android.credentialmanager.model.get.CredentialEntryInfo
+import com.android.credentialmanager.model.get.ActionEntryInfo
+import com.android.credentialmanager.model.get.AuthenticationEntryInfo
+import com.android.credentialmanager.model.Request
+import androidx.test.filters.SmallTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.mockito.kotlin.mock
+import org.junit.runner.RunWith
+import com.android.credentialmanager.model.CredentialType
+import com.google.common.truth.Truth.assertThat
+import com.android.credentialmanager.ui.mappers.toGet
+import com.android.credentialmanager.model.get.ProviderInfo
+import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry.PerUserNameEntries
+
+/** Unit tests for [CredentialSelectorUiStateGetMapper]. */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CredentialSelectorUiStateGetMapperTest {
+
+ private val mDrawable = mock<Drawable>()
+
+ private val actionEntryInfo =
+ ActionEntryInfo(
+ providerId = "",
+ entryKey = "",
+ entrySubkey = "",
+ pendingIntent = null,
+ fillInIntent = null,
+ title = "title",
+ icon = mDrawable,
+ subTitle = "subtitle",
+ )
+
+ private val authenticationEntryInfo =
+ AuthenticationEntryInfo(
+ providerId = "",
+ entryKey = "",
+ entrySubkey = "",
+ pendingIntent = null,
+ fillInIntent = null,
+ title = "title",
+ providerDisplayName = "",
+ icon = mDrawable,
+ isUnlockedAndEmpty = true,
+ isLastUnlocked = true
+ )
+
+ val passkeyCredentialEntryInfo =
+ createCredentialEntryInfo(credentialType = CredentialType.PASSKEY, userName = "userName")
+
+ val unknownCredentialEntryInfo =
+ createCredentialEntryInfo(credentialType = CredentialType.UNKNOWN, userName = "userName2")
+
+ val passwordCredentialEntryInfo =
+ createCredentialEntryInfo(credentialType = CredentialType.PASSWORD, userName = "userName")
+
+ val recentlyUsedPasskeyCredential =
+ createCredentialEntryInfo(credentialType =
+ CredentialType.PASSKEY, lastUsedTimeMillis = 2L, userName = "userName")
+
+ val recentlyUsedPasswordCredential =
+ createCredentialEntryInfo(credentialType =
+ CredentialType.PASSWORD, lastUsedTimeMillis = 2L, userName = "userName")
+
+ val credentialList1 = listOf(
+ passkeyCredentialEntryInfo,
+ passwordCredentialEntryInfo
+ )
+
+ val credentialList2 = listOf(
+ passkeyCredentialEntryInfo,
+ passwordCredentialEntryInfo,
+ recentlyUsedPasskeyCredential,
+ unknownCredentialEntryInfo,
+ recentlyUsedPasswordCredential
+ )
+
+ @Test
+ fun `On primary screen, just one account returns SingleEntry`() {
+ val getCredentialUiState = Request.Get(
+ token = null,
+ resultReceiver = null,
+ finalResponseReceiver = null,
+ providerInfos = listOf(createProviderInfo(credentialList1))).toGet(isPrimary = true)
+
+ assertThat(getCredentialUiState).isEqualTo(
+ CredentialSelectorUiState.Get.SingleEntry(passkeyCredentialEntryInfo)
+ ) // prefer passkey over password for selected credential
+ }
+
+ @Test
+ fun `On primary screen, multiple accounts returns SingleEntryPerAccount`() {
+ val getCredentialUiState = Request.Get(
+ token = null,
+ resultReceiver = null,
+ finalResponseReceiver = null,
+ providerInfos = listOf(createProviderInfo(listOf(passkeyCredentialEntryInfo,
+ unknownCredentialEntryInfo)))).toGet(isPrimary = true)
+
+ assertThat(getCredentialUiState).isEqualTo(
+ CredentialSelectorUiState.Get.SingleEntryPerAccount(
+ sortedEntries = listOf(
+ passkeyCredentialEntryInfo, // userName
+ unknownCredentialEntryInfo // userName2
+ ),
+ authenticationEntryList = listOf(authenticationEntryInfo)
+ )) // prefer passkey from account 1, then unknown from account 2
+ }
+
+ @Test
+ fun `On secondary screen, a MultipleEntry is returned`() {
+ val getCredentialUiState = Request.Get(
+ token = null,
+ resultReceiver = null,
+ finalResponseReceiver = null,
+ providerInfos = listOf(createProviderInfo(credentialList1))).toGet(isPrimary = false)
+
+ assertThat(getCredentialUiState).isEqualTo(
+ CredentialSelectorUiState.Get.MultipleEntry(
+ listOf(PerUserNameEntries("userName", listOf(
+ passkeyCredentialEntryInfo,
+ passwordCredentialEntryInfo))
+ ),
+ listOf(actionEntryInfo),
+ listOf(authenticationEntryInfo)
+ ))
+ }
+
+ @Test
+ fun `Returned multiple entry is sorted by credentialType and lastUsedTimeMillis`() {
+ val getCredentialUiState = Request.Get(
+ token = null,
+ resultReceiver = null,
+ finalResponseReceiver = null,
+ providerInfos = listOf(createProviderInfo(credentialList1),
+ createProviderInfo(credentialList2))).toGet(isPrimary = false)
+
+ assertThat(getCredentialUiState).isEqualTo(
+ CredentialSelectorUiState.Get.MultipleEntry(
+ listOf(
+ PerUserNameEntries("userName",
+ listOf(
+ recentlyUsedPasskeyCredential, // from provider 2
+ passkeyCredentialEntryInfo, // from provider 1 or 2
+ passkeyCredentialEntryInfo, // from provider 1 or 2
+ recentlyUsedPasswordCredential, // from provider 2
+ passwordCredentialEntryInfo, // from provider 1 or 2
+ passwordCredentialEntryInfo, // from provider 1 or 2
+ )),
+ PerUserNameEntries("userName2", listOf(unknownCredentialEntryInfo)),
+ ),
+ listOf(actionEntryInfo, actionEntryInfo),
+ listOf(authenticationEntryInfo, authenticationEntryInfo)
+ )
+ )
+ }
+
+ fun createCredentialEntryInfo(
+ userName: String,
+ credentialType: CredentialType = CredentialType.PASSKEY,
+ lastUsedTimeMillis: Long = 0L
+ ): CredentialEntryInfo =
+ CredentialEntryInfo(
+ providerId = "",
+ entryKey = "",
+ entrySubkey = "",
+ pendingIntent = null,
+ fillInIntent = null,
+ credentialType = credentialType,
+ rawCredentialType = "",
+ credentialTypeDisplayName = "",
+ providerDisplayName = "",
+ userName = userName,
+ displayName = "",
+ icon = mDrawable,
+ shouldTintIcon = false,
+ lastUsedTimeMillis = Instant.ofEpochMilli(lastUsedTimeMillis),
+ isAutoSelectable = true,
+ entryGroupId = "",
+ isDefaultIconPreferredAsSingleProvider = false,
+ affiliatedDomain = "",
+ )
+
+ fun createProviderInfo(credentials: List<CredentialEntryInfo> = listOf()): ProviderInfo =
+ ProviderInfo(
+ id = "providerInfo",
+ icon = mDrawable,
+ displayName = "displayName",
+ credentialEntryList = credentials,
+ authenticationEntryList = listOf(authenticationEntryInfo),
+ remoteEntry = null,
+ actionEntryList = listOf(actionEntryInfo)
+ )
+}
diff --git a/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorViewModelTest.kt b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorViewModelTest.kt
new file mode 100644
index 0000000..b79f34c
--- /dev/null
+++ b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorViewModelTest.kt
@@ -0,0 +1,241 @@
+/*
+ * 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.credentialmanager
+
+import org.mockito.kotlin.whenever
+import com.android.credentialmanager.model.EntryInfo
+import com.android.credentialmanager.model.Request
+import androidx.test.filters.SmallTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.Before
+import java.util.Collections.emptyList
+import org.junit.runner.RunWith
+import android.content.Intent
+import com.android.credentialmanager.client.CredentialManagerClient
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.launchIn
+import android.credentials.selection.BaseDialogResult
+import com.google.common.truth.Truth.assertThat
+import org.mockito.kotlin.doReturn
+import kotlinx.coroutines.Job
+import org.junit.After
+import org.robolectric.shadows.ShadowLooper
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** Unit tests for [CredentialSelectorViewModel]. */
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CredentialSelectorViewModelTest {
+ private val testScope = TestScope(UnconfinedTestDispatcher())
+
+ private val stateFlow: MutableStateFlow<Request?> = MutableStateFlow(Request.Create(null))
+ private val credentialManagerClient = mock<CredentialManagerClient>{
+ on { requests } doReturn stateFlow
+ }
+ private val mViewModel = CredentialSelectorViewModel(credentialManagerClient)
+ private lateinit var job: Job
+
+ val testEntryInfo =
+ EntryInfo(
+ providerId = "",
+ entryKey = "",
+ entrySubkey = "",
+ pendingIntent = null,
+ fillInIntent = null,
+ shouldTerminateUiUponSuccessfulProviderResult = true)
+
+ @Before
+ fun setUp() {
+ job = checkNotNull(mViewModel).uiState.launchIn(testScope)
+ }
+
+ @After
+ fun teardown() {
+ job.cancel()
+ }
+
+ @Test
+ fun `Setting state to idle when receiving null request`() {
+ stateFlow.value = null
+ ShadowLooper.idleMainLooper()
+
+ assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Idle)
+ }
+
+ @Test
+ fun `Setting state to cancel when receiving Cancel request`() {
+ stateFlow.value = Request.Cancel(appName = "appName", token = null)
+ ShadowLooper.idleMainLooper()
+
+ assertThat(mViewModel.uiState.value)
+ .isEqualTo(CredentialSelectorUiState.Cancel("appName"))
+ }
+
+ @Test
+ fun `Setting state to create when receiving Create request`() {
+ stateFlow.value = Request.Create(token = null)
+ ShadowLooper.idleMainLooper()
+
+ assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Create)
+ }
+
+ @Test
+ fun `Closing app when receiving Close request`() {
+ stateFlow.value = Request.Close(token = null)
+ ShadowLooper.idleMainLooper()
+
+ assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+ }
+
+ @Test
+ fun `Updates request`() {
+ val intent = Intent()
+
+ mViewModel.updateRequest(intent)
+
+ verify(credentialManagerClient).updateRequest(intent)
+ }
+
+ @Test
+ fun `Back on a single entry screen closes app`() {
+ mViewModel.openSecondaryScreen()
+ stateFlow.value = Request.Get(
+ token = null,
+ resultReceiver = null,
+ finalResponseReceiver = null,
+ providerInfos = emptyList())
+
+ mViewModel.back()
+ ShadowLooper.idleMainLooper()
+
+ assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+ }
+
+ @Test
+ fun `Back on a multiple entry screen gets us back to a primary screen`() {
+ mViewModel.openSecondaryScreen()
+ stateFlow.value = Request.Get(
+ token = null,
+ resultReceiver = null,
+ finalResponseReceiver = null,
+ providerInfos = emptyList())
+
+ mViewModel.back()
+ ShadowLooper.idleMainLooper()
+
+ assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+ }
+
+ @Test
+ fun `Back on create request state closes app`() {
+ stateFlow.value = Request.Create(token = null)
+
+ mViewModel.back()
+ ShadowLooper.idleMainLooper()
+
+ assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+ }
+
+ @Test
+ fun `Back on close request state closes app`() {
+ stateFlow.value = Request.Close(token = null)
+
+ mViewModel.back()
+ ShadowLooper.idleMainLooper()
+
+ assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+ }
+
+ @Test
+ fun `Back on cancel request state closes app`() {
+ stateFlow.value = Request.Cancel(appName = "", token = null)
+
+ mViewModel.back()
+ ShadowLooper.idleMainLooper()
+
+ assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+ }
+
+ @Test
+ fun `Back on idle request state closes app`() {
+ stateFlow.value = null
+
+ mViewModel.back()
+ ShadowLooper.idleMainLooper()
+
+ assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+ }
+
+ @Test
+ fun `Cancel closes the app`() {
+ mViewModel.cancel()
+ ShadowLooper.idleMainLooper()
+
+ verify(credentialManagerClient).sendError(BaseDialogResult.RESULT_CODE_DIALOG_USER_CANCELED)
+ assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+ }
+
+ @Test
+ fun `Send entry selection result closes app and calls client method`() {
+ whenever(credentialManagerClient.sendEntrySelectionResult(
+ entryInfo = testEntryInfo,
+ resultCode = null,
+ resultData = null,
+ isAutoSelected = false
+ )).thenReturn(true)
+
+ mViewModel.sendSelectionResult(
+ entryInfo = testEntryInfo,
+ resultCode = null,
+ resultData = null,
+ isAutoSelected = false)
+ ShadowLooper.idleMainLooper()
+
+ verify(credentialManagerClient).sendEntrySelectionResult(
+ testEntryInfo, null, null, false
+ )
+ assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+ }
+
+ @Test
+ fun `Send entry selection result does not close app on false return`() {
+ whenever(credentialManagerClient.sendEntrySelectionResult(
+ entryInfo = testEntryInfo,
+ resultCode = null,
+ resultData = null,
+ isAutoSelected = false
+ )).thenReturn(false)
+ stateFlow.value = Request.Create(null)
+
+ mViewModel.sendSelectionResult(entryInfo = testEntryInfo, resultCode = null,
+ resultData = null, isAutoSelected = false)
+ ShadowLooper.idleMainLooper()
+
+ verify(credentialManagerClient).sendEntrySelectionResult(
+ entryInfo = testEntryInfo,
+ resultCode = null,
+ resultData = null,
+ isAutoSelected = false
+ )
+ assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Create)
+ }
+}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 66be7ba..9d97763 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -59,8 +59,10 @@
isPrimaryScreen,
shouldClose
) { request, isPrimary, shouldClose ->
+ Log.d(TAG, "Request updated: " + request?.toString() +
+ " isClose: " + shouldClose.toString() +
+ " isPrimaryScreen: " + isPrimary.toString())
if (shouldClose) {
- Log.d(TAG, "Request finished, closing ")
return@combine Close
}
@@ -139,7 +141,10 @@
data object Idle : CredentialSelectorUiState()
sealed class Get : CredentialSelectorUiState() {
data class SingleEntry(val entry: CredentialEntryInfo) : Get()
- data class SingleEntryPerAccount(val sortedEntries: List<CredentialEntryInfo>) : Get()
+ data class SingleEntryPerAccount(
+ val sortedEntries: List<CredentialEntryInfo>,
+ val authenticationEntryList: List<AuthenticationEntryInfo>,
+ ) : Get()
data class MultipleEntry(
val accounts: List<PerUserNameEntries>,
val actionEntryList: List<ActionEntryInfo>,
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
index 405de1d3..bf4c988 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
@@ -29,6 +29,7 @@
import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
import androidx.wear.compose.navigation.rememberSwipeDismissableNavHostState
import com.android.credentialmanager.CredentialSelectorUiState
+import com.android.credentialmanager.CredentialSelectorUiState.Get.SingleEntryPerAccount
import com.android.credentialmanager.CredentialSelectorUiState.Get.SingleEntry
import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry
import com.android.credentialmanager.CredentialSelectorViewModel
@@ -45,6 +46,8 @@
import com.android.credentialmanager.model.CredentialType
import com.android.credentialmanager.model.EntryInfo
import com.android.credentialmanager.ui.screens.multiple.MultiCredentialsFoldScreen
+import com.android.credentialmanager.ui.screens.multiple.MultiCredentialsFlattenScreen
+
@OptIn(ExperimentalHorologistApi::class)
@Composable
@@ -78,59 +81,70 @@
scrollable(Screen.SinglePasskeyScreen.route) {
SinglePasskeyScreen(
- credentialSelectorUiState = viewModel.uiState.value as SingleEntry,
+ entry = (remember { uiState } as SingleEntry).entry,
columnState = it.columnState,
+ flowEngine = flowEngine,
)
}
scrollable(Screen.SignInWithProviderScreen.route) {
SignInWithProviderScreen(
- credentialSelectorUiState = viewModel.uiState.value as SingleEntry,
+ entry = (remember { uiState } as SingleEntry).entry,
columnState = it.columnState,
+ flowEngine = flowEngine,
)
}
scrollable(Screen.MultipleCredentialsScreenFold.route) {
MultiCredentialsFoldScreen(
- credentialSelectorUiState = viewModel.uiState.value as MultipleEntry,
- screenIcon = null,
+ credentialSelectorUiState = (remember { uiState } as SingleEntryPerAccount),
columnState = it.columnState,
+ flowEngine = flowEngine,
+ )
+ }
+
+ scrollable(Screen.MultipleCredentialsScreenFlatten.route) {
+ MultiCredentialsFlattenScreen(
+ credentialSelectorUiState = (remember { uiState } as MultipleEntry),
+ columnState = it.columnState,
+ flowEngine = flowEngine,
)
}
}
- BackHandler(true) {
- viewModel.back()
- }
- Log.d(TAG, "uiState change, state: $uiState")
- when (val state = uiState) {
- CredentialSelectorUiState.Idle -> {
- if (navController.currentDestination?.route != Screen.Loading.route) {
- navController.navigateToLoading()
+ BackHandler(true) {
+ viewModel.back()
+ }
+ Log.d(TAG, "uiState change, state: $uiState")
+ when (val state = uiState) {
+ CredentialSelectorUiState.Idle -> {
+ if (navController.currentDestination?.route != Screen.Loading.route) {
+ navController.navigateToLoading()
+ }
+ }
+
+ is CredentialSelectorUiState.Get -> {
+ handleGetNavigation(
+ navController = navController,
+ state = state,
+ onCloseApp = onCloseApp,
+ selectEntry = selectEntry
+ )
+ }
+
+ CredentialSelectorUiState.Create -> {
+ // TODO: b/301206624 - Implement create flow
+ onCloseApp()
+ }
+
+ is CredentialSelectorUiState.Cancel -> {
+ onCloseApp()
+ }
+
+ CredentialSelectorUiState.Close -> {
+ onCloseApp()
}
}
- is CredentialSelectorUiState.Get -> {
- handleGetNavigation(
- navController = navController,
- state = state,
- onCloseApp = onCloseApp,
- selectEntry = selectEntry
- )
- }
-
- CredentialSelectorUiState.Create -> {
- // TODO: b/301206624 - Implement create flow
- onCloseApp()
- }
-
- is CredentialSelectorUiState.Cancel -> {
- onCloseApp()
- }
-
- CredentialSelectorUiState.Close -> {
- onCloseApp()
- }
}
-}
private fun handleGetNavigation(
navController: NavController,
@@ -157,13 +171,12 @@
}
}
- is MultipleEntry -> {
- navController.navigateToMultipleCredentialsFoldScreen()
- }
+ is SingleEntryPerAccount -> {
+ navController.navigateToMultipleCredentialsFoldScreen()
+ }
- else -> {
- // TODO: b/301206470 - Implement other get flows
- onCloseApp()
+ is MultipleEntry -> {
+ navController.navigateToMultipleCredentialsFlattenScreen()
+ }
}
}
-}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
index 03b0931..7a936b6 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
@@ -32,11 +32,14 @@
return if (isPrimary) {
if (accounts.size == 1) {
CredentialSelectorUiState.Get.SingleEntry(
- accounts[0].value.minWith(comparator)
+ entry = accounts[0].value.minWith(comparator)
)
} else {
CredentialSelectorUiState.Get.SingleEntryPerAccount(
- accounts.map { it.value.minWith(comparator) }.sortedWith(comparator)
+ sortedEntries = accounts.map {
+ it.value.minWith(comparator)
+ }.sortedWith(comparator),
+ authenticationEntryList = providerInfos.flatMap { it.authenticationEntryList }
)
}
} else {
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
index 11188b4..d54103c 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
@@ -15,112 +15,53 @@
*/
package com.android.credentialmanager.ui.screens.multiple
-import android.graphics.drawable.Drawable
-import com.android.credentialmanager.ui.screens.UiState
-import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.navigation.NavHostController
-import androidx.navigation.compose.rememberNavController
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.Text
import com.android.credentialmanager.ui.components.SignInHeader
import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry
+import com.android.credentialmanager.FlowEngine
import com.android.credentialmanager.R
-import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
-import com.android.credentialmanager.model.get.ActionEntryInfo
import com.android.credentialmanager.model.get.CredentialEntryInfo
import com.android.credentialmanager.ui.components.CredentialsScreenChip
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.ScalingLazyColumn
import com.google.android.horologist.compose.layout.ScalingLazyColumnState
-
/**
* Screen that shows multiple credentials to select from, grouped by accounts
*
* @param credentialSelectorUiState The app bar view model.
- * @param screenIcon The view model corresponding to the home page.
* @param columnState ScalingLazyColumn configuration to be be applied
* @param modifier styling for composable
- * @param viewModel ViewModel that updates ui state for this screen
- * @param navController handles navigation events from this screen
+ * @param flowEngine [FlowEngine] that updates ui state for this screen
*/
@OptIn(ExperimentalHorologistApi::class)
@Composable
fun MultiCredentialsFlattenScreen(
credentialSelectorUiState: MultipleEntry,
- screenIcon: Drawable?,
columnState: ScalingLazyColumnState,
- modifier: Modifier = Modifier,
- viewModel: MultiCredentialsFlattenViewModel = hiltViewModel(),
- navController: NavHostController = rememberNavController(),
+ flowEngine: FlowEngine,
) {
- val uiState by viewModel.uiState.collectAsStateWithLifecycle()
-
- when (val state = uiState) {
- UiState.CredentialScreen -> {
- MultiCredentialsFlattenScreen(
- state = credentialSelectorUiState,
- columnState = columnState,
- screenIcon = screenIcon,
- onActionEntryClicked = viewModel::onActionEntryClicked,
- onCredentialClicked = viewModel::onCredentialClicked,
- modifier = modifier,
- )
- }
-
- is UiState.CredentialSelected -> {
- val launcher = rememberLauncherForActivityResult(
- StartBalIntentSenderForResultContract()
- ) {
- viewModel.onInfoRetrieved(it.resultCode, null)
- }
-
- SideEffect {
- state.intentSenderRequest?.let {
- launcher.launch(it)
- }
- }
- }
-
- UiState.Cancel -> {
- navController.popBackStack()
- }
- }
-}
-
-@OptIn(ExperimentalHorologistApi::class)
-@Composable
-fun MultiCredentialsFlattenScreen(
- state: MultipleEntry,
- columnState: ScalingLazyColumnState,
- screenIcon: Drawable?,
- onActionEntryClicked: (entryInfo: ActionEntryInfo) -> Unit,
- onCredentialClicked: (entryInfo: CredentialEntryInfo) -> Unit,
- modifier: Modifier,
-) {
+ val selectEntry = flowEngine.getEntrySelector()
ScalingLazyColumn(
columnState = columnState,
- modifier = modifier.fillMaxSize(),
+ modifier = Modifier.fillMaxSize(),
) {
item {
// make this credential specific if all credentials are same
SignInHeader(
- icon = screenIcon,
+ icon = null,
title = stringResource(R.string.sign_in_options_title),
)
}
- state.accounts.forEach { userNameEntries ->
+ credentialSelectorUiState.accounts.forEach { userNameEntries ->
item {
Text(
text = userNameEntries.userName,
@@ -135,17 +76,16 @@
item {
CredentialsScreenChip(
label = credential.userName,
- onClick = { onCredentialClicked(credential) },
- secondaryLabel = credential.userName,
+ onClick = { selectEntry(credential, false) },
+ secondaryLabel = credential.credentialTypeDisplayName,
icon = credential.icon,
- modifier = modifier,
)
}
}
}
item {
Text(
- text = "Manage Sign-ins",
+ text = stringResource(R.string.provider_list_title),
modifier = Modifier
.padding(top = 6.dp)
.padding(horizontal = 10.dp),
@@ -153,14 +93,13 @@
)
}
- state.actionEntryList.forEach {
+ credentialSelectorUiState.actionEntryList.forEach {actionEntry ->
item {
CredentialsScreenChip(
- label = it.title,
- onClick = { onActionEntryClicked(it) },
+ label = actionEntry.title,
+ onClick = { selectEntry(actionEntry, false) },
secondaryLabel = null,
- icon = it.icon,
- modifier = modifier,
+ icon = actionEntry.icon,
)
}
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenViewModel.kt
deleted file mode 100644
index ee5f3f4..0000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenViewModel.kt
+++ /dev/null
@@ -1,75 +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.credentialmanager.ui.screens.multiple
-
-import android.content.Intent
-import android.credentials.selection.ProviderPendingIntentResponse
-import android.credentials.selection.UserSelectionDialogResult
-import androidx.lifecycle.ViewModel
-import com.android.credentialmanager.client.CredentialManagerClient
-import com.android.credentialmanager.ktx.getIntentSenderRequest
-import com.android.credentialmanager.model.Request
-import com.android.credentialmanager.model.get.ActionEntryInfo
-import com.android.credentialmanager.model.get.CredentialEntryInfo
-import com.android.credentialmanager.ui.screens.UiState
-import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import javax.inject.Inject
-
-/** ViewModel for [MultiCredentialsFlattenScreen].*/
-@HiltViewModel
-class MultiCredentialsFlattenViewModel @Inject constructor(
- private val credentialManagerClient: CredentialManagerClient,
-) : ViewModel() {
-
- private lateinit var requestGet: Request.Get
- private lateinit var entryInfo: CredentialEntryInfo
-
- private val _uiState =
- MutableStateFlow<UiState>(UiState.CredentialScreen)
- val uiState: StateFlow<UiState> = _uiState
-
- fun onCredentialClicked(entryInfo: CredentialEntryInfo) {
- this.entryInfo = entryInfo
- _uiState.value = UiState.CredentialSelected(
- intentSenderRequest = entryInfo.getIntentSenderRequest()
- )
- }
-
- fun onCancelClicked() {
- _uiState.value = UiState.Cancel
- }
-
- fun onInfoRetrieved(
- resultCode: Int? = null,
- resultData: Intent? = null,
- ) {
- val userSelectionDialogResult = UserSelectionDialogResult(
- requestGet.token,
- entryInfo.providerId,
- entryInfo.entryKey,
- entryInfo.entrySubkey,
- if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
- )
- credentialManagerClient.sendResult(userSelectionDialogResult)
- }
-
- fun onActionEntryClicked(actionEntryInfo: ActionEntryInfo) {
- // TODO(b/322797032)to be filled out
- }
-}
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
index 5515c86..6f32c99 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
@@ -16,24 +16,15 @@
package com.android.credentialmanager.ui.screens.multiple
-import com.android.credentialmanager.ui.screens.UiState
-import android.graphics.drawable.Drawable
-import androidx.activity.compose.rememberLauncherForActivityResult
import com.android.credentialmanager.R
import androidx.compose.ui.res.stringResource
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.navigation.NavHostController
-import androidx.navigation.compose.rememberNavController
import com.android.credentialmanager.CredentialSelectorUiState
-import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
+import com.android.credentialmanager.FlowEngine
import com.android.credentialmanager.model.get.CredentialEntryInfo
import com.android.credentialmanager.ui.components.DismissChip
import com.android.credentialmanager.ui.components.CredentialsScreenChip
@@ -49,74 +40,22 @@
* Screen that shows multiple credentials to select from.
*
* @param credentialSelectorUiState The app bar view model.
- * @param screenIcon The view model corresponding to the home page.
* @param columnState ScalingLazyColumn configuration to be be applied
- * @param modifier styling for composable
- * @param viewModel ViewModel that updates ui state for this screen
- * @param navController handles navigation events from this screen
*/
@OptIn(ExperimentalHorologistApi::class)
@Composable
fun MultiCredentialsFoldScreen(
- credentialSelectorUiState: CredentialSelectorUiState.Get.MultipleEntry,
- screenIcon: Drawable?,
+ credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntryPerAccount,
columnState: ScalingLazyColumnState,
- modifier: Modifier = Modifier,
- viewModel: MultiCredentialsFoldViewModel = hiltViewModel(),
- navController: NavHostController = rememberNavController(),
+ flowEngine: FlowEngine,
) {
- val uiState by viewModel.uiState.collectAsStateWithLifecycle()
-
- when (val state = uiState) {
- UiState.CredentialScreen -> {
- MultiCredentialsFoldScreen(
- state = credentialSelectorUiState,
- onSignInOptionsClicked = viewModel::onSignInOptionsClicked,
- onCredentialClicked = viewModel::onCredentialClicked,
- onCancelClicked = viewModel::onCancelClicked,
- screenIcon = screenIcon,
- columnState = columnState,
- modifier = modifier
- )
- }
-
- is UiState.CredentialSelected -> {
- val launcher = rememberLauncherForActivityResult(
- StartBalIntentSenderForResultContract()
- ) {
- viewModel.onInfoRetrieved(it.resultCode, null)
- }
-
- SideEffect {
- state.intentSenderRequest?.let {
- launcher.launch(it)
- }
- }
- }
-
- UiState.Cancel -> {
- navController.popBackStack()
- }
- }
-}
-
-@OptIn(ExperimentalHorologistApi::class)
-@Composable
-fun MultiCredentialsFoldScreen(
- state: CredentialSelectorUiState.Get.MultipleEntry,
- onSignInOptionsClicked: () -> Unit,
- onCredentialClicked: (entryInfo: CredentialEntryInfo) -> Unit,
- onCancelClicked: () -> Unit,
- screenIcon: Drawable?,
- columnState: ScalingLazyColumnState,
- modifier: Modifier,
-) {
+ val selectEntry = flowEngine.getEntrySelector()
ScalingLazyColumn(
columnState = columnState,
- modifier = modifier.fillMaxSize(),
+ modifier = Modifier.fillMaxSize(),
) {
// flatten all credentials into one
- val credentials = state.accounts.flatMap { it.sortedCredentialEntryList }
+ val credentials = credentialSelectorUiState.sortedEntries
item {
var title = stringResource(R.string.choose_sign_in_title)
if (credentials.all{ it.credentialType == CredentialType.PASSKEY }) {
@@ -126,7 +65,7 @@
}
SignInHeader(
- icon = screenIcon,
+ icon = null,
title = title,
modifier = Modifier
.padding(top = 6.dp),
@@ -137,22 +76,24 @@
item {
CredentialsScreenChip(
label = credential.userName,
- onClick = { onCredentialClicked(credential) },
+ onClick = { selectEntry(credential, false) },
secondaryLabel = credential.credentialTypeDisplayName,
icon = credential.icon,
)
}
}
- state.authenticationEntryList.forEach { authenticationEntryInfo ->
+ credentialSelectorUiState.authenticationEntryList.forEach { authenticationEntryInfo ->
item {
LockedProviderChip(authenticationEntryInfo) {
- // TODO(b/322797032) invoke LockedProviderScreen here using flow engine
- }
+ selectEntry(authenticationEntryInfo, false) }
}
}
-
- item { SignInOptionsChip(onSignInOptionsClicked)}
- item { DismissChip(onCancelClicked) }
+ item {
+ SignInOptionsChip { flowEngine.openSecondaryScreen() }
+ }
+ item {
+ DismissChip { flowEngine.cancel() }
+ }
}
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldViewModel.kt
deleted file mode 100644
index 627a63d..0000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldViewModel.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.screens.multiple
-
-import android.content.Intent
-import android.credentials.selection.ProviderPendingIntentResponse
-import android.credentials.selection.UserSelectionDialogResult
-import androidx.lifecycle.ViewModel
-import com.android.credentialmanager.client.CredentialManagerClient
-import com.android.credentialmanager.ktx.getIntentSenderRequest
-import com.android.credentialmanager.model.Request
-import com.android.credentialmanager.model.get.CredentialEntryInfo
-import com.android.credentialmanager.ui.screens.UiState
-import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import javax.inject.Inject
-
-/** ViewModel for [MultiCredentialsFoldScreen].*/
-@HiltViewModel
-class MultiCredentialsFoldViewModel @Inject constructor(
- private val credentialManagerClient: CredentialManagerClient,
-) : ViewModel() {
-
- private lateinit var requestGet: Request.Get
- private lateinit var entryInfo: CredentialEntryInfo
-
- private val _uiState =
- MutableStateFlow<UiState>(UiState.CredentialScreen)
- val uiState: StateFlow<UiState> = _uiState
-
- fun onCredentialClicked(entryInfo: CredentialEntryInfo) {
- this.entryInfo = entryInfo
- _uiState.value = UiState.CredentialSelected(
- intentSenderRequest = entryInfo.getIntentSenderRequest()
- )
- }
-
- fun onSignInOptionsClicked() {
- // TODO(b/322797032) Implement navigation route for single credential screen to multiple
- // credentials
- }
-
- fun onCancelClicked() {
- _uiState.value = UiState.Cancel
- }
-
- fun onInfoRetrieved(
- resultCode: Int? = null,
- resultData: Intent? = null,
- ) {
- val userSelectionDialogResult = UserSelectionDialogResult(
- requestGet.token,
- entryInfo.providerId,
- entryInfo.entryKey,
- entryInfo.entrySubkey,
- if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
- )
- credentialManagerClient.sendResult(userSelectionDialogResult)
- }
-}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
index b2595a1..56b1c2e 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
@@ -19,92 +19,38 @@
package com.android.credentialmanager.ui.screens.single.passkey
import androidx.compose.foundation.layout.Column
-import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.navigation.NavHostController
-import androidx.navigation.compose.rememberNavController
-import com.android.credentialmanager.CredentialSelectorUiState
+import com.android.credentialmanager.FlowEngine
import com.android.credentialmanager.model.get.CredentialEntryInfo
import com.android.credentialmanager.R
-import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
import com.android.credentialmanager.ui.components.AccountRow
import com.android.credentialmanager.ui.components.ContinueChip
import com.android.credentialmanager.ui.components.DismissChip
import com.android.credentialmanager.ui.components.SignInHeader
import com.android.credentialmanager.ui.components.SignInOptionsChip
import com.android.credentialmanager.ui.screens.single.SingleAccountScreen
-import com.android.credentialmanager.ui.screens.UiState
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.ScalingLazyColumnState
/**
* Screen that shows sign in with provider credential.
*
- * @param credentialSelectorUiState The app bar view model.
+ * @param entry The password entry
* @param columnState ScalingLazyColumn configuration to be be applied to SingleAccountScreen
* @param modifier styling for composable
- * @param viewModel ViewModel that updates ui state for this screen
- * @param navController handles navigation events from this screen
+ * @param flowEngine [FlowEngine] that updates ui state for this screen
*/
@OptIn(ExperimentalHorologistApi::class)
@Composable
fun SinglePasskeyScreen(
- credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntry,
- columnState: ScalingLazyColumnState,
- modifier: Modifier = Modifier,
- viewModel: SinglePasskeyScreenViewModel = hiltViewModel(),
- navController: NavHostController = rememberNavController(),
-) {
- viewModel.initialize(credentialSelectorUiState.entry)
-
- val uiState by viewModel.uiState.collectAsStateWithLifecycle()
-
- when (val state = uiState) {
- UiState.CredentialScreen -> {
- SinglePasskeyScreen(
- credentialSelectorUiState.entry,
- columnState,
- modifier,
- viewModel
- )
- }
-
- is UiState.CredentialSelected -> {
- val launcher = rememberLauncherForActivityResult(
- StartBalIntentSenderForResultContract()
- ) {
- viewModel.onPasskeyInfoRetrieved(it.resultCode, null)
- }
-
- SideEffect {
- state.intentSenderRequest?.let {
- launcher.launch(it)
- }
- }
- }
-
- UiState.Cancel -> {
- // TODO(b/322797032) add valid navigation path here for going back
- navController.popBackStack()
- }
- }
-}
-
-@OptIn(ExperimentalHorologistApi::class)
-@Composable
-fun SinglePasskeyScreen(
entry: CredentialEntryInfo,
columnState: ScalingLazyColumnState,
modifier: Modifier = Modifier,
- viewModel: SinglePasskeyScreenViewModel,
+ flowEngine: FlowEngine,
) {
SingleAccountScreen(
headerContent = {
@@ -114,27 +60,21 @@
)
},
accountContent = {
- if (entry.displayName != null) {
- AccountRow(
+ AccountRow(
primaryText = checkNotNull(entry.displayName),
secondaryText = entry.userName,
modifier = Modifier.padding(top = 10.dp),
)
- } else {
- AccountRow(
- primaryText = entry.userName,
- modifier = Modifier.padding(top = 10.dp),
- )
- }
},
columnState = columnState,
modifier = modifier.padding(horizontal = 10.dp)
) {
item {
+ val selectEntry = flowEngine.getEntrySelector()
Column {
- ContinueChip(viewModel::onContinueClick)
- SignInOptionsChip(viewModel::onSignInOptionsClick)
- DismissChip(viewModel::onDismissClick)
+ ContinueChip { selectEntry(entry, false) }
+ SignInOptionsChip{ flowEngine.openSecondaryScreen() }
+ DismissChip { flowEngine.cancel() }
}
}
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreenViewModel.kt
deleted file mode 100644
index 37ffaca..0000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreenViewModel.kt
+++ /dev/null
@@ -1,79 +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.credentialmanager.ui.screens.single.passkey
-
-import android.content.Intent
-import android.credentials.selection.UserSelectionDialogResult
-import android.credentials.selection.ProviderPendingIntentResponse
-import androidx.annotation.MainThread
-import androidx.lifecycle.ViewModel
-import com.android.credentialmanager.ktx.getIntentSenderRequest
-import com.android.credentialmanager.model.Request
-import com.android.credentialmanager.client.CredentialManagerClient
-import com.android.credentialmanager.model.get.CredentialEntryInfo
-import dagger.hilt.android.lifecycle.HiltViewModel
-import com.android.credentialmanager.ui.screens.UiState
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import javax.inject.Inject
-
-@HiltViewModel
-class SinglePasskeyScreenViewModel @Inject constructor(
- private val credentialManagerClient: CredentialManagerClient,
-) : ViewModel() {
-
- private val _uiState =
- MutableStateFlow<UiState>(UiState.CredentialScreen)
- val uiState: StateFlow<UiState> = _uiState
-
- private lateinit var requestGet: Request.Get
- private lateinit var entryInfo: CredentialEntryInfo
-
-
- @MainThread
- fun initialize(entry: CredentialEntryInfo) {
- this.entryInfo = entry
- }
-
- fun onDismissClick() {
- _uiState.value = UiState.Cancel
- }
-
- fun onContinueClick() {
- _uiState.value = UiState.CredentialSelected(
- intentSenderRequest = entryInfo.getIntentSenderRequest()
- )
- }
-
- fun onSignInOptionsClick() {
- }
-
- fun onPasskeyInfoRetrieved(
- resultCode: Int? = null,
- resultData: Intent? = null,
- ) {
- val userSelectionDialogResult = UserSelectionDialogResult(
- requestGet.token,
- entryInfo.providerId,
- entryInfo.entryKey,
- entryInfo.entrySubkey,
- if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
- )
- credentialManagerClient.sendResult(userSelectionDialogResult)
- }
-}
-
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt
index b0ece0d..3a86feb 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt
@@ -16,100 +16,43 @@
package com.android.credentialmanager.ui.screens.single.signInWithProvider
-import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.navigation.NavHostController
-import androidx.navigation.compose.rememberNavController
-import com.android.credentialmanager.CredentialSelectorUiState
+import com.android.credentialmanager.FlowEngine
import com.android.credentialmanager.model.get.CredentialEntryInfo
-import com.android.credentialmanager.R
-import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
import com.android.credentialmanager.ui.components.AccountRow
import com.android.credentialmanager.ui.components.ContinueChip
import com.android.credentialmanager.ui.components.DismissChip
import com.android.credentialmanager.ui.components.SignInHeader
import com.android.credentialmanager.ui.components.SignInOptionsChip
import com.android.credentialmanager.ui.screens.single.SingleAccountScreen
-import com.android.credentialmanager.ui.screens.UiState
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.ScalingLazyColumnState
/**
* Screen that shows sign in with provider credential.
*
- * @param credentialSelectorUiState The app bar view model.
+ * @param entry The password entry.
* @param columnState ScalingLazyColumn configuration to be be applied to SingleAccountScreen
* @param modifier styling for composable
- * @param viewModel ViewModel that updates ui state for this screen
- * @param navController handles navigation events from this screen
+ * @param flowEngine [FlowEngine] that updates ui state for this screen
*/
@OptIn(ExperimentalHorologistApi::class)
@Composable
fun SignInWithProviderScreen(
- credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntry,
- columnState: ScalingLazyColumnState,
- modifier: Modifier = Modifier,
- viewModel: SignInWithProviderViewModel = hiltViewModel(),
- navController: NavHostController = rememberNavController(),
-) {
- viewModel.initialize(credentialSelectorUiState.entry)
-
- val uiState by viewModel.uiState.collectAsStateWithLifecycle()
-
- when (uiState) {
- UiState.CredentialScreen -> {
- SignInWithProviderScreen(
- credentialSelectorUiState.entry,
- columnState,
- modifier,
- viewModel
- )
- }
-
- is UiState.CredentialSelected -> {
- val launcher = rememberLauncherForActivityResult(
- StartBalIntentSenderForResultContract()
- ) {
- viewModel.onInfoRetrieved(it.resultCode, null)
- }
-
- SideEffect {
- (uiState as UiState.CredentialSelected).intentSenderRequest?.let {
- launcher.launch(it)
- }
- }
- }
-
- UiState.Cancel -> {
- // TODO(b/322797032) add valid navigation path here for going back
- navController.popBackStack()
- }
- }
-}
-
-@OptIn(ExperimentalHorologistApi::class)
-@Composable
-fun SignInWithProviderScreen(
entry: CredentialEntryInfo,
columnState: ScalingLazyColumnState,
modifier: Modifier = Modifier,
- viewModel: SignInWithProviderViewModel,
+ flowEngine: FlowEngine,
) {
SingleAccountScreen(
headerContent = {
SignInHeader(
icon = entry.icon,
- title = stringResource(R.string.use_sign_in_with_provider_title,
- entry.providerDisplayName),
+ title = entry.providerDisplayName,
)
},
accountContent = {
@@ -130,12 +73,13 @@
columnState = columnState,
modifier = modifier.padding(horizontal = 10.dp)
) {
- item {
- Column {
- ContinueChip(viewModel::onContinueClick)
- SignInOptionsChip(viewModel::onSignInOptionsClick)
- DismissChip(viewModel::onDismissClick)
- }
- }
+ item {
+ val selectEntry = flowEngine.getEntrySelector()
+ Column {
+ ContinueChip { selectEntry(entry, false) }
+ SignInOptionsChip{ flowEngine.openSecondaryScreen() }
+ DismissChip { flowEngine.cancel() }
+ }
+ }
}
}
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderViewModel.kt
deleted file mode 100644
index 7ba45e5..0000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderViewModel.kt
+++ /dev/null
@@ -1,81 +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.credentialmanager.ui.screens.single.signInWithProvider
-
-import android.content.Intent
-import android.credentials.selection.ProviderPendingIntentResponse
-import android.credentials.selection.UserSelectionDialogResult
-import androidx.annotation.MainThread
-import androidx.lifecycle.ViewModel
-import com.android.credentialmanager.ktx.getIntentSenderRequest
-import com.android.credentialmanager.model.Request
-import com.android.credentialmanager.client.CredentialManagerClient
-import com.android.credentialmanager.model.get.CredentialEntryInfo
-import dagger.hilt.android.lifecycle.HiltViewModel
-import com.android.credentialmanager.ui.screens.UiState
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import javax.inject.Inject
-
-/** ViewModel for [SignInWithProviderScreen].*/
-@HiltViewModel
-class SignInWithProviderViewModel @Inject constructor(
- private val credentialManagerClient: CredentialManagerClient,
-) : ViewModel() {
-
- private val _uiState =
- MutableStateFlow<UiState>(UiState.CredentialScreen)
- val uiState: StateFlow<UiState> = _uiState
-
- private lateinit var requestGet: Request.Get
- private lateinit var entryInfo: CredentialEntryInfo
-
- @MainThread
- fun initialize(entry: CredentialEntryInfo) {
- this.entryInfo = entry
- }
-
- fun onDismissClick() {
- _uiState.value = UiState.Cancel
- }
-
- fun onContinueClick() {
- _uiState.value = UiState.CredentialSelected(
- intentSenderRequest = entryInfo.getIntentSenderRequest()
- )
- }
-
- fun onSignInOptionsClick() {
- // TODO(b/322797032) Implement navigation route for single credential screen to multiple
- // credentials
- }
-
- fun onInfoRetrieved(
- resultCode: Int? = null,
- resultData: Intent? = null,
- ) {
- val userSelectionDialogResult = UserSelectionDialogResult(
- requestGet.token,
- entryInfo.providerId,
- entryInfo.entryKey,
- entryInfo.entrySubkey,
- if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
- )
- credentialManagerClient.sendResult(userSelectionDialogResult)
- }
-}
-
diff --git a/packages/EasterEgg/res/values-night/styles.xml b/packages/EasterEgg/res/values-night/styles.xml
index 4edf692..6ea2eae 100644
--- a/packages/EasterEgg/res/values-night/styles.xml
+++ b/packages/EasterEgg/res/values-night/styles.xml
@@ -15,7 +15,7 @@
-->
<resources>
<style name="AppTheme" parent="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen">
- <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
+ <item name="android:windowLayoutInDisplayCutoutMode">always</item>
<item name="android:windowLightNavigationBar">false</item>
</style>
</resources>
diff --git a/packages/EasterEgg/res/values/styles.xml b/packages/EasterEgg/res/values/styles.xml
index e576526..4a2cb48f6 100644
--- a/packages/EasterEgg/res/values/styles.xml
+++ b/packages/EasterEgg/res/values/styles.xml
@@ -16,7 +16,7 @@
<resources>
<style name="AppTheme" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar.Fullscreen">
- <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
+ <item name="android:windowLayoutInDisplayCutoutMode">always</item>
<item name="android:windowLightNavigationBar">true</item>
</style>
diff --git a/packages/PackageInstaller/res/values-watch/themes.xml b/packages/PackageInstaller/res/values-watch/themes.xml
index 5e52008..814d08a 100644
--- a/packages/PackageInstaller/res/values-watch/themes.xml
+++ b/packages/PackageInstaller/res/values-watch/themes.xml
@@ -16,5 +16,11 @@
-->
<resources>
- <style name="DialogWhenLarge" parent="@android:style/Theme.DeviceDefault.NoActionBar"/>
+ <style name="Theme.AlertDialogActivity"
+ parent="@android:style/Theme.DeviceDefault.Dialog.Alert">
+ <item name="alertDialogStyle">@style/AlertDialog</item>
+ <item name="android:windowActionBar">false</item>
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowAnimationStyle">@null</item>
+ </style>
</resources>
diff --git a/packages/SettingsLib/DataStore/README.md b/packages/SettingsLib/DataStore/README.md
new file mode 100644
index 0000000..30cb993
--- /dev/null
+++ b/packages/SettingsLib/DataStore/README.md
@@ -0,0 +1,164 @@
+# Datastore library
+
+This library aims to manage datastore in a consistent way.
+
+## Overview
+
+A datastore is required to extend the `BackupRestoreStorage` class and implement
+either `Observable` or `KeyedObservable` interface, which enforces:
+
+- Backup and restore: Datastore should support
+ [data backup](https://developer.android.com/guide/topics/data/backup) to
+ preserve user experiences on a new device.
+- Observer pattern: The
+ [observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) allows to
+ monitor data change in the datastore and
+ - trigger
+ [BackupManager.dataChanged](https://developer.android.com/reference/android/app/backup/BackupManager#dataChanged\(\))
+ automatically.
+ - track data change event to log metrics.
+ - update internal state and take action.
+
+### Backup and restore
+
+The Android backup framework provides
+[BackupAgentHelper](https://developer.android.com/reference/android/app/backup/BackupAgentHelper)
+and
+[BackupHelper](https://developer.android.com/reference/android/app/backup/BackupHelper)
+to back up a datastore. However, there are several caveats when implement
+`BackupHelper`:
+
+- performBackup: The data is updated incrementally but it is not well
+ documented. The `ParcelFileDescriptor` state parameters are normally ignored
+ and data is updated even there is no change.
+- restoreEntity: The implementation must take care not to seek or close the
+ underlying data source, nor read more than size() bytes from the stream when
+ restore (see
+ [BackupDataInputStream](https://developer.android.com/reference/android/app/backup/BackupDataInputStream)).
+ It is possible a `BackupHelper` prevents other `BackupHelper`s from
+ restoring data.
+- writeNewStateDescription: Existing implementations rarely notice that this
+ callback is invoked after all entities are restored, and check if necessary
+ data are all restored in `restoreEntity` (e.g.
+ [BatteryBackupHelper](https://cs.android.com/android/platform/superproject/main/+/main:packages/apps/Settings/src/com/android/settings/fuelgauge/BatteryBackupHelper.java;l=144;drc=cca804e1ed504e2d477be1e3db00fb881ca32736)),
+ which is not robust sometimes.
+
+This library provides more clear API and offers some improvements:
+
+- The implementation only needs to focus on the `BackupRestoreEntity`
+ interface. The `InputStream` of restore will ensure bounded data are read,
+ and close the stream will be no-op.
+- The library computes checksum of the backup data automatically, so that
+ unchanged data will not be sent to Android backup system.
+- Data compression is supported:
+ - ZIP best compression is enabled by default, no extra effort needs to be
+ taken.
+ - It is safe to switch between compression and no compression in future,
+ the backup data will add 1 byte header to recognize the codec.
+ - To support other compression algorithms, simply wrap over the
+ `InputStream` and `OutputStream`. Actually, the checksum is computed in
+ this way by
+ [CheckedInputStream](https://developer.android.com/reference/java/util/zip/CheckedInputStream)
+ and
+ [CheckedOutputStream](https://developer.android.com/reference/java/util/zip/CheckedOutputStream),
+ see `BackupRestoreStorage` implementation for more details.
+- Enhanced forward compatibility for file is enabled: If a backup includes
+ data that didn't exist in earlier versions of the app, the data can still be
+ successfully restored in those older versions. This is achieved by extending
+ the `BackupRestoreFileStorage` class, and `BackupRestoreFileArchiver` will
+ treat each file as an entity and do the backup / restore.
+- Manual `BackupManager.dataChanged` call is unnecessary now, the library will
+ do the invocation (see next section).
+
+### Observer pattern
+
+Manual `BackupManager.dataChanged` call is required by current backup framework.
+In practice, it is found that `SharedPreferences` usages foget to invoke the
+API. Besides, there are common use cases to log metrics when data is changed.
+Consequently, observer pattern is employed to resolve the issues.
+
+If the datastore is key-value based (e.g. `SharedPreferences`), implements the
+`KeyedObservable` interface to offer fine-grained observer. Otherwise,
+implements `Observable`. The library provides thread-safe implementations
+(`KeyedDataObservable` / `DataObservable`), and Kotlin delegation will be
+helpful.
+
+Keep in mind that the implementation should call `KeyedObservable.notifyChange`
+/ `Observable.notifyChange` whenever internal data is changed, so that the
+registered observer will be notified properly.
+
+## Usage and example
+
+For `SharedPreferences` use case, leverage the `SharedPreferencesStorage`. To
+back up other file based storage, extend the `BackupRestoreFileStorage` class.
+
+Here is an example of customized datastore, which has a string to back up:
+
+```kotlin
+class MyDataStore : ObservableBackupRestoreStorage() {
+ // Another option is make it a StringEntity type and maintain a String field inside StringEntity
+ @Volatile // backup/restore happens on Binder thread
+ var data: String? = null
+ private set
+
+ fun setData(data: String?) {
+ this.data = data
+ notifyChange(ChangeReason.UPDATE)
+ }
+
+ override val name: String
+ get() = "MyData"
+
+ override fun createBackupRestoreEntities(): List<BackupRestoreEntity> =
+ listOf(StringEntity("data"))
+
+ private inner class StringEntity(override val key: String) : BackupRestoreEntity {
+ override fun backup(
+ backupContext: BackupContext,
+ outputStream: OutputStream,
+ ) =
+ if (data != null) {
+ outputStream.write(data!!.toByteArray(UTF_8))
+ EntityBackupResult.UPDATE
+ } else {
+ EntityBackupResult.DELETE
+ }
+
+ override fun restore(restoreContext: RestoreContext, inputStream: InputStream) {
+ data = String(inputStream.readAllBytes(), UTF_8)
+ // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you
+ }
+ }
+
+ override fun onRestoreFinished() {
+ // TODO: Update state with the restored data. Use this callback instead "restore()" in case
+ // the restore action involves several entities.
+ // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you
+ }
+}
+```
+
+In the application class:
+
+```kotlin
+class MyApplication : Application() {
+ override fun onCreate() {
+ super.onCreate();
+ BackupRestoreStorageManager.getInstance(this).add(MyDataStore());
+ }
+}
+```
+
+In the custom `BackupAgentHelper` class:
+
+```kotlin
+class MyBackupAgentHelper : BackupAgentHelper() {
+ override fun onCreate() {
+ BackupRestoreStorageManager.getInstance(this).addBackupAgentHelpers(this);
+ }
+
+ override fun onRestoreFinished() {
+ BackupRestoreStorageManager.getInstance(this).onRestoreFinished();
+ }
+}
+```
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileArchiver.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileArchiver.kt
index 621a8d7..9d3fb66 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileArchiver.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileArchiver.kt
@@ -62,7 +62,7 @@
}
Log.i(LOG_TAG, "[$name] Restore ${data.size()} bytes for $key to $file")
val inputStream = LimitedNoCloseInputStream(data)
- checksum.reset()
+ val checksum = createChecksum()
val checkedInputStream = CheckedInputStream(inputStream, checksum)
try {
val codec = BackupCodec.fromId(checkedInputStream.read().toByte())
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt
index ea2fb72..c4c00cb 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt
@@ -36,6 +36,7 @@
import java.util.zip.CRC32
import java.util.zip.CheckedInputStream
import java.util.zip.CheckedOutputStream
+import java.util.zip.Checksum
internal const val LOG_TAG = "BackupRestoreStorage"
@@ -54,15 +55,6 @@
*/
abstract val name: String
- private val entities: List<BackupRestoreEntity> by lazy { createBackupRestoreEntities() }
-
- /**
- * Checksum of the data.
- *
- * Always call [java.util.zip.Checksum.reset] before using it.
- */
- protected val checksum = CRC32()
-
/**
* Entity states represented by checksum.
*
@@ -70,13 +62,16 @@
*/
protected val entityStates = MutableScatterMap<String, Long>()
+ /** Entities created by [createBackupRestoreEntities]. This field is for restore only. */
+ private var entities: List<BackupRestoreEntity>? = null
+
/** Entities to back up and restore. */
abstract fun createBackupRestoreEntities(): List<BackupRestoreEntity>
/** Default codec used to encode/decode the entity data. */
open fun defaultCodec(): BackupCodec = BackupZipCodec.BEST_COMPRESSION
- override fun performBackup(
+ final override fun performBackup(
oldState: ParcelFileDescriptor?,
data: BackupDataOutput,
newState: ParcelFileDescriptor,
@@ -88,6 +83,9 @@
return
}
Log.i(LOG_TAG, "[$name] Backup start")
+ val checksum = createChecksum()
+ // recreate entities for backup to avoid stale states
+ val entities = createBackupRestoreEntities()
for (entity in entities) {
val key = entity.key
val outputStream = ByteArrayOutputStream()
@@ -103,7 +101,8 @@
}
when (result) {
EntityBackupResult.UPDATE -> {
- if (updateEntityState(key)) {
+ val value = checksum.value
+ if (entityStates.put(key, value) != value) {
val payload = outputStream.toByteArray()
val size = payload.size
data.writeEntityHeader(key, size)
@@ -126,15 +125,10 @@
}
}
}
- newState.writeEntityStates(entityStates)
+ newState.writeAndClearEntityStates()
Log.i(LOG_TAG, "[$name] Backup end")
}
- private fun updateEntityState(key: String): Boolean {
- val value = checksum.value
- return entityStates.put(key, value) != value
- }
-
/** Returns if backup is enabled. */
open fun enableBackup(backupContext: BackupContext): Boolean = true
@@ -144,13 +138,14 @@
return codec.encode(outputStream)
}
+ /** This callback is invoked for every backed up entity. */
override fun restoreEntity(data: BackupDataInputStream) {
val key = data.key
if (!enableRestore()) {
Log.i(LOG_TAG, "[$name] Restore disabled, ignore entity $key")
return
}
- val entity = entities.firstOrNull { it.key == key }
+ val entity = ensureEntities().firstOrNull { it.key == key }
if (entity == null) {
Log.w(LOG_TAG, "[$name] Cannot find handler for entity $key")
return
@@ -159,7 +154,7 @@
val restoreContext = RestoreContext(key)
val codec = entity.codec() ?: defaultCodec()
val inputStream = LimitedNoCloseInputStream(data)
- checksum.reset()
+ val checksum = createChecksum()
val checkedInputStream = CheckedInputStream(inputStream, checksum)
try {
entity.restore(restoreContext, wrapRestoreInputStream(codec, checkedInputStream))
@@ -169,6 +164,9 @@
}
}
+ private fun ensureEntities(): List<BackupRestoreEntity> =
+ entities ?: createBackupRestoreEntities().also { entities = it }
+
/** Returns if restore is enabled. */
open fun enableRestore(): Boolean = true
@@ -185,7 +183,8 @@
}
final override fun writeNewStateDescription(newState: ParcelFileDescriptor) {
- newState.writeEntityStates(entityStates)
+ entities = null // clear to reduce memory footprint
+ newState.writeAndClearEntityStates()
onRestoreFinished()
}
@@ -223,24 +222,29 @@
}
}
- private fun ParcelFileDescriptor.writeEntityStates(state: MutableScatterMap<String, Long>) {
+ private fun ParcelFileDescriptor.writeAndClearEntityStates() {
// do not close the streams
val fileOutputStream = FileOutputStream(fileDescriptor)
val dataOutputStream = DataOutputStream(fileOutputStream)
try {
dataOutputStream.writeByte(STATE_VERSION.toInt())
- dataOutputStream.writeInt(state.size)
- state.forEach { key, value ->
+ dataOutputStream.writeInt(entityStates.size)
+ entityStates.forEach { key, value ->
dataOutputStream.writeUTF(key)
dataOutputStream.writeLong(value)
}
} catch (exception: Exception) {
Log.e(LOG_TAG, "[$name] Fail to write state file", exception)
}
+ entityStates.clear()
+ entityStates.trim() // trim to reduce memory footprint
}
companion object {
private const val STATE_VERSION: Byte = 0
+
+ /** Checksum for entity backup data. */
+ fun createChecksum(): Checksum = CRC32()
}
}
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt
index 0e39493..cfdcaff 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt
@@ -26,23 +26,10 @@
/** Manager of [BackupRestoreStorage]. */
class BackupRestoreStorageManager private constructor(private val application: Application) {
- private val storages = ConcurrentHashMap<String, BackupRestoreStorage>()
+ private val storageWrappers = ConcurrentHashMap<String, StorageWrapper>()
private val executor = MoreExecutors.directExecutor()
- private val observer = Observer { reason -> notifyBackupManager(null, reason) }
-
- private val keyedObserver =
- KeyedObserver<Any?> { key, reason -> notifyBackupManager(key, reason) }
-
- private fun notifyBackupManager(key: Any?, reason: Int) {
- // prefer not triggering backup immediately after restore
- if (reason == ChangeReason.RESTORE) return
- // TODO: log storage name
- Log.d(LOG_TAG, "Notify BackupManager data changed for change: key=$key")
- BackupManager.dataChanged(application.packageName)
- }
-
/**
* Adds all the registered [BackupRestoreStorage] as the helpers of given [BackupAgentHelper].
*
@@ -52,7 +39,8 @@
*/
fun addBackupAgentHelpers(backupAgentHelper: BackupAgentHelper) {
val fileStorages = mutableListOf<BackupRestoreFileStorage>()
- for ((keyPrefix, storage) in storages) {
+ for ((keyPrefix, storageWrapper) in storageWrappers) {
+ val storage = storageWrapper.storage
if (storage is BackupRestoreFileStorage) {
fileStorages.add(storage)
} else {
@@ -70,15 +58,8 @@
* The observers of the storages will be notified.
*/
fun onRestoreFinished() {
- for (storage in storages.values) {
- storage.notifyRestoreFinished()
- }
- }
-
- private fun BackupRestoreStorage.notifyRestoreFinished() {
- when (this) {
- is KeyedObservable<*> -> notifyChange(ChangeReason.RESTORE)
- is Observable -> notifyChange(ChangeReason.RESTORE)
+ for (storageWrapper in storageWrappers.values) {
+ storageWrapper.notifyRestoreFinished()
}
}
@@ -99,50 +80,82 @@
fun add(storage: BackupRestoreStorage) {
if (storage is BackupRestoreFileStorage) storage.checkFilePaths()
val name = storage.name
- val oldStorage = storages.put(name, storage)
+ val oldStorage = storageWrappers.put(name, StorageWrapper(storage))?.storage
if (oldStorage != null) {
throw IllegalStateException(
"Storage name '$name' conflicts:\n\told: $oldStorage\n\tnew: $storage"
)
}
- storage.addObserver()
- }
-
- private fun BackupRestoreStorage.addObserver() {
- when (this) {
- is KeyedObservable<*> -> addObserver(keyedObserver, executor)
- is Observable -> addObserver(observer, executor)
- else ->
- throw IllegalArgumentException(
- "$this does not implement either KeyedObservable or Observable"
- )
- }
}
/** Removes all the storages. */
fun removeAll() {
- for ((name, _) in storages) remove(name)
+ for ((name, _) in storageWrappers) remove(name)
}
/** Removes storage with given name. */
fun remove(name: String): BackupRestoreStorage? {
- val storage = storages.remove(name)
- storage?.removeObserver()
- return storage
- }
-
- private fun BackupRestoreStorage.removeObserver() {
- when (this) {
- is KeyedObservable<*> -> removeObserver(keyedObserver)
- is Observable -> removeObserver(observer)
- }
+ val storageWrapper = storageWrappers.remove(name)
+ storageWrapper?.removeObserver()
+ return storageWrapper?.storage
}
/** Returns storage with given name. */
- fun get(name: String): BackupRestoreStorage? = storages[name]
+ fun get(name: String): BackupRestoreStorage? = storageWrappers[name]?.storage
/** Returns storage with given name, exception is raised if not found. */
- fun getOrThrow(name: String): BackupRestoreStorage = storages[name]!!
+ fun getOrThrow(name: String): BackupRestoreStorage = storageWrappers[name]!!.storage
+
+ private inner class StorageWrapper(val storage: BackupRestoreStorage) :
+ Observer, KeyedObserver<Any?> {
+ init {
+ when (storage) {
+ is KeyedObservable<*> -> storage.addObserver(this, executor)
+ is Observable -> storage.addObserver(this, executor)
+ else ->
+ throw IllegalArgumentException(
+ "$this does not implement either KeyedObservable or Observable"
+ )
+ }
+ }
+
+ override fun onChanged(reason: Int) = onKeyChanged(null, reason)
+
+ override fun onKeyChanged(key: Any?, reason: Int) {
+ notifyBackupManager(key, reason)
+ }
+
+ private fun notifyBackupManager(key: Any?, reason: Int) {
+ val name = storage.name
+ // prefer not triggering backup immediately after restore
+ if (reason == ChangeReason.RESTORE) {
+ Log.d(
+ LOG_TAG,
+ "Notify BackupManager dataChanged ignored for restore: storage=$name key=$key"
+ )
+ return
+ }
+ Log.d(
+ LOG_TAG,
+ "Notify BackupManager dataChanged: storage=$name key=$key reason=$reason"
+ )
+ BackupManager.dataChanged(application.packageName)
+ }
+
+ fun removeObserver() {
+ when (storage) {
+ is KeyedObservable<*> -> storage.removeObserver(this)
+ is Observable -> storage.removeObserver(this)
+ }
+ }
+
+ fun notifyRestoreFinished() {
+ when (storage) {
+ is KeyedObservable<*> -> storage.notifyChange(ChangeReason.RESTORE)
+ is Observable -> storage.notifyChange(ChangeReason.RESTORE)
+ }
+ }
+ }
companion object {
@Volatile private var instance: BackupRestoreStorageManager? = null
diff --git a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
index 18e8fc3..f47041d 100644
--- a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
+++ b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
@@ -85,7 +85,7 @@
*/
@RequiresApi(Build.VERSION_CODES.M)
public static void sendShowAdminSupportDetailsIntent(Context context, EnforcedAdmin admin) {
- final Intent intent = getShowAdminSupportDetailsIntent(context, admin);
+ final Intent intent = getShowAdminSupportDetailsIntent(admin);
int targetUserId = UserHandle.myUserId();
if (admin != null) {
if (admin.user != null
@@ -98,9 +98,16 @@
}
/**
- * Gets the intent to trigger the {@code android.settings.ShowAdminSupportDetailsDialog}.
+ * @deprecated No context needed. Use {@link #getShowAdminSupportDetailsIntent(EnforcedAdmin)}.
*/
public static Intent getShowAdminSupportDetailsIntent(Context context, EnforcedAdmin admin) {
+ return getShowAdminSupportDetailsIntent(admin);
+ }
+
+ /**
+ * Gets the intent to trigger the {@code android.settings.ShowAdminSupportDetailsDialog}.
+ */
+ public static Intent getShowAdminSupportDetailsIntent(EnforcedAdmin admin) {
final Intent intent = new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);
if (admin != null) {
if (admin.component != null) {
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenPathManager.kt
similarity index 69%
rename from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt
rename to packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenPathManager.kt
index f5fba7f..d590760 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenPathManager.kt
@@ -17,28 +17,25 @@
package com.android.settingslib.spa.screenshot.util
import androidx.test.platform.app.InstrumentationRegistry
-import platform.test.screenshot.GoldenImagePathManager
+import platform.test.screenshot.GoldenPathManager
import platform.test.screenshot.PathConfig
-/** A [GoldenImagePathManager] that should be used for all Settings screenshot tests. */
-class SettingsGoldenImagePathManager(
- pathConfig: PathConfig,
- assetsPathRelativeToBuildRoot: String
-) :
- GoldenImagePathManager(
+/** A [GoldenPathManager] that should be used for all Settings screenshot tests. */
+class SettingsGoldenPathManager(pathConfig: PathConfig, assetsPathRelativeToBuildRoot: String) :
+ GoldenPathManager(
appContext = InstrumentationRegistry.getInstrumentation().context,
assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
deviceLocalPath =
- InstrumentationRegistry.getInstrumentation()
- .targetContext
- .filesDir
- .absolutePath
- .toString() + "/settings_screenshots",
+ InstrumentationRegistry.getInstrumentation()
+ .targetContext
+ .filesDir
+ .absolutePath
+ .toString() + "/settings_screenshots",
pathConfig = pathConfig,
) {
override fun toString(): String {
// This string is appended to all actual/expected screenshots on the device, so make sure
// it is a static value.
- return "SettingsGoldenImagePathManager"
+ return "SettingsGoldenPathManager"
}
}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
index ae85675..16f6b5e 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
@@ -44,7 +44,7 @@
private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
private val screenshotRule =
ScreenshotTestRule(
- SettingsGoldenImagePathManager(
+ SettingsGoldenPathManager(
getEmulatedDevicePathConfig(emulationSpec),
assetsPathRelativeToBuildRoot
)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreference.kt
index 9866023..3f74ed5 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreference.kt
@@ -36,7 +36,7 @@
TwoTargetPreference(
title = title,
summary = summary,
- onClick = onClick,
+ primaryOnClick = onClick,
icon = icon,
) {
IconButton(onClick = onButtonClick) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
index 3216e37..3f68804 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
@@ -34,7 +34,8 @@
internal fun TwoTargetPreference(
title: String,
summary: () -> String,
- onClick: () -> Unit,
+ primaryEnabled: () -> Boolean = { true },
+ primaryOnClick: (() -> Unit)?,
icon: @Composable (() -> Unit)? = null,
widget: @Composable () -> Unit,
) {
@@ -50,7 +51,8 @@
override val title = title
override val summary = summary
override val icon = icon
- override val onClick = onClick
+ override val enabled = primaryEnabled
+ override val onClick = primaryOnClick
}
)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
index 7eed745..8b546b4 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
@@ -24,13 +24,15 @@
fun TwoTargetSwitchPreference(
model: SwitchPreferenceModel,
icon: @Composable (() -> Unit)? = null,
- onClick: () -> Unit,
+ primaryEnabled: () -> Boolean = { true },
+ primaryOnClick: (() -> Unit)?,
) {
EntryHighlight {
TwoTargetPreference(
title = model.title,
summary = model.summary,
- onClick = onClick,
+ primaryEnabled = primaryEnabled,
+ primaryOnClick = primaryOnClick,
icon = icon,
) {
SettingsSwitch(
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreferenceTest.kt
index 3455851..0acf287 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreferenceTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreferenceTest.kt
@@ -23,6 +23,7 @@
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotEnabled
import androidx.compose.ui.test.assertIsOff
import androidx.compose.ui.test.assertIsOn
import androidx.compose.ui.test.isToggleable
@@ -46,7 +47,7 @@
TestTwoTargetSwitchPreference(changeable = true)
}
- composeTestRule.onNodeWithText("TwoTargetSwitchPreference").assertIsDisplayed()
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
}
@Test
@@ -79,7 +80,7 @@
}
@Test
- fun clickable_canBeClick() {
+ fun clickable_primaryEnabled_canBeClick() {
var clicked = false
composeTestRule.setContent {
TestTwoTargetSwitchPreference(changeable = false) {
@@ -87,26 +88,54 @@
}
}
- composeTestRule.onNodeWithText("TwoTargetSwitchPreference").performClick()
+ composeTestRule.onNodeWithText(TITLE).performClick()
assertThat(clicked).isTrue()
}
-}
-@Composable
-private fun TestTwoTargetSwitchPreference(
- changeable: Boolean,
- onClick: () -> Unit = {},
-) {
- var checked by rememberSaveable { mutableStateOf(false) }
- TwoTargetSwitchPreference(
- model = remember {
- object : SwitchPreferenceModel {
- override val title = "TwoTargetSwitchPreference"
- override val checked = { checked }
- override val changeable = { changeable }
- override val onCheckedChange = { newChecked: Boolean -> checked = newChecked }
+ @Test
+ fun clickable_primaryNotEnabled_assertIsNotEnabled() {
+ composeTestRule.setContent {
+ TestTwoTargetSwitchPreference(changeable = false, primaryEnabled = false)
+ }
+
+ composeTestRule.onNodeWithText(TITLE).assertIsNotEnabled()
+ }
+
+ @Test
+ fun clickable_primaryNotEnabled_canNotBeClick() {
+ var clicked = false
+ composeTestRule.setContent {
+ TestTwoTargetSwitchPreference(changeable = false, primaryEnabled = false) {
+ clicked = true
}
- },
- onClick = onClick,
- )
+ }
+
+ composeTestRule.onNodeWithText(TITLE).performClick()
+ assertThat(clicked).isFalse()
+ }
+
+ @Composable
+ private fun TestTwoTargetSwitchPreference(
+ changeable: Boolean,
+ primaryEnabled: Boolean = true,
+ primaryOnClick: () -> Unit = {},
+ ) {
+ var checked by rememberSaveable { mutableStateOf(false) }
+ TwoTargetSwitchPreference(
+ model = remember {
+ object : SwitchPreferenceModel {
+ override val title = TITLE
+ override val checked = { checked }
+ override val changeable = { changeable }
+ override val onCheckedChange = { newChecked: Boolean -> checked = newChecked }
+ }
+ },
+ primaryEnabled = { primaryEnabled },
+ primaryOnClick = primaryOnClick,
+ )
+ }
+
+ private companion object {
+ const val TITLE = "Title"
+ }
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
index bea14c3..5c2d770 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
@@ -38,6 +38,6 @@
override val onCheckedChange = onCheckedChange
},
icon = { AppIcon(record.app, SettingsDimension.appIconItemSize) },
- onClick = onClick,
+ primaryOnClick = onClick,
)
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt
index ac85dd4..389b3c1 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt
@@ -54,14 +54,27 @@
return
}
val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
- val restrictedSwitchModel = remember(restrictedMode) {
+ val restrictedModel = remember(restrictedMode) {
RestrictedPreferenceModel(model, restrictedMode)
}
- restrictedSwitchModel.RestrictionWrapper {
- Preference(restrictedSwitchModel)
+ restrictedModel.RestrictionWrapper {
+ Preference(restrictedModel)
}
}
+internal fun RestrictedMode?.restrictEnabled(enabled: () -> Boolean) = when (this) {
+ NoRestricted -> enabled
+ else -> ({ false })
+}
+
+internal fun <T> RestrictedMode?.restrictOnClick(onClick: T): T? = when (this) {
+ NoRestricted -> onClick
+ // Need to passthrough onClick for clickable semantics, although since enabled is false so
+ // this will not be called.
+ BaseUserRestricted -> onClick
+ else -> null
+}
+
private class RestrictedPreferenceModel(
model: PreferenceModel,
private val restrictedMode: RestrictedMode?,
@@ -69,19 +82,8 @@
override val title = model.title
override val summary = model.summary
override val icon = model.icon
-
- override val enabled = when (restrictedMode) {
- NoRestricted -> model.enabled
- else -> ({ false })
- }
-
- override val onClick = when (restrictedMode) {
- NoRestricted -> model.onClick
- // Need to passthrough onClick for clickable semantics, although since enabled is false so
- // this will not be called.
- BaseUserRestricted -> model.onClick
- else -> null
- }
+ override val enabled = restrictedMode.restrictEnabled(model.enabled)
+ override val onClick = restrictedMode.restrictOnClick(model.onClick)
@Composable
fun RestrictionWrapper(content: @Composable () -> Unit) {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
index aba3460..5dfecb0 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
@@ -60,18 +60,9 @@
is BlockedByEcm -> model.checked
}
- override val changeable = if (restrictedMode is NoRestricted) model.changeable else ({ false })
+ override val changeable = restrictedMode.restrictEnabled(model.changeable)
- override val onCheckedChange = when (restrictedMode) {
- null -> null
- is NoRestricted -> model.onCheckedChange
- // Need to passthrough onCheckedChange for toggleable semantics, although since changeable
- // is false so this will not be called.
- is BaseUserRestricted -> model.onCheckedChange
- // Pass null since semantics ToggleableState is provided in RestrictionWrapper.
- is BlockedByAdmin -> null
- is BlockedByEcm -> null
- }
+ override val onCheckedChange = restrictedMode.restrictOnClick(model.onCheckedChange)
@Composable
fun RestrictionWrapper(content: @Composable () -> Unit) {
@@ -116,13 +107,12 @@
companion object {
@Composable
- fun RestrictionsProviderFactory.RestrictedSwitchWrapper(
+ fun RestrictedSwitchWrapper(
model: SwitchPreferenceModel,
- restrictions: Restrictions,
+ restrictedMode: RestrictedMode?,
content: @Composable (SwitchPreferenceModel) -> Unit,
) {
val context = LocalContext.current
- val restrictedMode = rememberRestrictedMode(restrictions).value
val restrictedSwitchPreferenceModel = remember(restrictedMode) {
RestrictedSwitchPreferenceModel(context, model, restrictedMode)
}
@@ -131,6 +121,15 @@
}
}
+ @Composable
+ fun RestrictionsProviderFactory.RestrictedSwitchWrapper(
+ model: SwitchPreferenceModel,
+ restrictions: Restrictions,
+ content: @Composable (SwitchPreferenceModel) -> Unit,
+ ) {
+ RestrictedSwitchWrapper(model, rememberRestrictedMode(restrictions).value, content)
+ }
+
fun getSummary(
context: Context,
restrictedModeSupplier: () -> RestrictedMode?,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt
new file mode 100644
index 0000000..e100773
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.preference
+
+import androidx.annotation.VisibleForTesting
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
+import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
+import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreferenceModel.Companion.RestrictedSwitchWrapper
+
+@Composable
+fun RestrictedTwoTargetSwitchPreference(
+ model: SwitchPreferenceModel,
+ icon: @Composable (() -> Unit)? = null,
+ restrictions: Restrictions,
+ primaryEnabled: () -> Boolean = { true },
+ primaryOnClick: (() -> Unit)?,
+) {
+ RestrictedTwoTargetSwitchPreference(
+ model = model,
+ icon = icon,
+ primaryEnabled = primaryEnabled,
+ primaryOnClick = primaryOnClick,
+ restrictions = restrictions,
+ restrictionsProviderFactory = ::RestrictionsProviderImpl,
+ )
+}
+
+@VisibleForTesting
+@Composable
+internal fun RestrictedTwoTargetSwitchPreference(
+ model: SwitchPreferenceModel,
+ icon: @Composable (() -> Unit)? = null,
+ primaryEnabled: () -> Boolean = { true },
+ primaryOnClick: (() -> Unit)?,
+ restrictions: Restrictions,
+ restrictionsProviderFactory: RestrictionsProviderFactory,
+) {
+ if (restrictions.isEmpty()) {
+ TwoTargetSwitchPreference(model, icon, primaryEnabled, primaryOnClick)
+ return
+ }
+ val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
+ RestrictedSwitchWrapper(model, restrictedMode) { restrictedModel ->
+ TwoTargetSwitchPreference(
+ model = restrictedModel,
+ icon = icon,
+ primaryEnabled = restrictedMode.restrictEnabled(primaryEnabled),
+ primaryOnClick = restrictedMode.restrictOnClick(primaryOnClick),
+ )
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreferenceTest.kt
new file mode 100644
index 0000000..bdff89f
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreferenceTest.kt
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.preference
+
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.isOff
+import androidx.compose.ui.test.isOn
+import androidx.compose.ui.test.isToggleable
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.tests.testutils.FakeBlockedByAdmin
+import com.android.settingslib.spaprivileged.tests.testutils.FakeBlockedByEcm
+import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class RestrictedTwoTargetSwitchPreferenceTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private val fakeBlockedByAdmin = FakeBlockedByAdmin()
+ private val fakeBlockedByEcm = FakeBlockedByEcm()
+
+ private val fakeRestrictionsProvider = FakeRestrictionsProvider()
+
+ private val switchPreferenceModel = object : SwitchPreferenceModel {
+ override val title = TITLE
+ private val checkedState = mutableStateOf(true)
+ override val checked = { checkedState.value }
+ override val onCheckedChange: (Boolean) -> Unit = { checkedState.value = it }
+ }
+
+ @Test
+ fun whenRestrictionsKeysIsEmpty_enabled() {
+ val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+ setContent(restrictions)
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+ composeTestRule.onNode(isOn()).assertIsDisplayed()
+ }
+
+ @Test
+ fun whenRestrictionsKeysIsEmpty_toggleable() {
+ val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+ setContent(restrictions)
+ composeTestRule.onNode(isToggleable()).performClick()
+
+ composeTestRule.onNode(isOff()).assertIsDisplayed()
+ }
+
+ @Test
+ fun whenNoRestricted_enabled() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+ setContent(restrictions)
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+ composeTestRule.onNode(isOn()).assertIsDisplayed().assertIsEnabled()
+ }
+
+ @Test
+ fun whenNoRestricted_toggleable() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+ setContent(restrictions)
+ composeTestRule.onNode(isToggleable()).performClick()
+
+ composeTestRule.onNode(isOff()).assertIsDisplayed()
+ }
+
+ @Test
+ fun whenBaseUserRestricted_disabled() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+ setContent(restrictions)
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsNotEnabled()
+ composeTestRule.onNode(isOff()).assertIsDisplayed()
+ }
+
+ @Test
+ fun whenBaseUserRestricted_notToggleable() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+ setContent(restrictions)
+ composeTestRule.onNode(isToggleable()).performClick()
+
+ composeTestRule.onNode(isOff()).assertIsDisplayed()
+ }
+
+ @Test
+ fun whenBlockedByAdmin_disabled() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+ setContent(restrictions)
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+ composeTestRule.onNodeWithText(FakeBlockedByAdmin.SUMMARY).assertIsDisplayed()
+ composeTestRule.onNode(isOn()).assertIsDisplayed().assertIsEnabled()
+ }
+
+ @Test
+ fun whenBlockedByAdmin_clickPrimary() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+ setContent(restrictions)
+ composeTestRule.onNodeWithText(TITLE).performClick()
+
+ assertThat(fakeBlockedByAdmin.sendShowAdminSupportDetailsIntentIsCalled).isTrue()
+ }
+
+ @Test
+ fun whenBlockedByAdmin_clickSwitch() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+ setContent(restrictions)
+ composeTestRule.onNode(isToggleable()).performClick()
+
+ assertThat(fakeBlockedByAdmin.sendShowAdminSupportDetailsIntentIsCalled).isTrue()
+ }
+
+ @Test
+ fun whenBlockedByEcm_disabled() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = fakeBlockedByEcm
+
+ setContent(restrictions)
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+ composeTestRule.onNodeWithText(FakeBlockedByEcm.SUMMARY).assertIsDisplayed()
+ composeTestRule.onNode(isOn()).assertIsDisplayed()
+ }
+
+ @Test
+ fun whenBlockedByEcm_click() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = fakeBlockedByEcm
+
+ setContent(restrictions)
+ composeTestRule.onRoot().performClick()
+
+ assertThat(fakeBlockedByEcm.showRestrictedSettingsDetailsIsCalled).isTrue()
+ }
+
+ private fun setContent(restrictions: Restrictions, primaryOnClick: (() -> Unit)? = {}) {
+ composeTestRule.setContent {
+ RestrictedTwoTargetSwitchPreference(
+ model = switchPreferenceModel,
+ primaryOnClick = primaryOnClick,
+ restrictions = restrictions,
+ ) { _, _ ->
+ fakeRestrictionsProvider
+ }
+ }
+ }
+
+ private companion object {
+ const val TITLE = "Title"
+ const val USER_ID = 0
+ const val RESTRICTION_KEY = "restriction_key"
+ }
+}
diff --git a/packages/SettingsLib/res/layout/dialog_with_icon.xml b/packages/SettingsLib/res/layout/dialog_with_icon.xml
index 3586dcb..b21895b 100644
--- a/packages/SettingsLib/res/layout/dialog_with_icon.xml
+++ b/packages/SettingsLib/res/layout/dialog_with_icon.xml
@@ -35,12 +35,14 @@
android:id="@+id/dialog_with_icon_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:hyphenationFrequency="fullFast"
android:gravity="center"
style="@style/DialogWithIconTitle"/>
<TextView
android:id="@+id/dialog_with_icon_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:hyphenationFrequency="fullFast"
android:gravity="center"
style="@style/TextAppearanceSmall"/>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index bd27c89..1118efc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -81,11 +81,13 @@
public static final int BROADCAST_STATE_UNKNOWN = 0;
public static final int BROADCAST_STATE_ON = 1;
public static final int BROADCAST_STATE_OFF = 2;
+
@Retention(RetentionPolicy.SOURCE)
@IntDef(
prefix = {"BROADCAST_STATE_"},
value = {BROADCAST_STATE_UNKNOWN, BROADCAST_STATE_ON, BROADCAST_STATE_OFF})
public @interface BroadcastState {}
+
private static final String SETTINGS_PKG = "com.android.settings";
private static final String TAG = "LocalBluetoothLeBroadcast";
private static final boolean DEBUG = BluetoothUtils.D;
@@ -1068,7 +1070,7 @@
return;
}
int fallbackActiveGroupId = getFallbackActiveGroupId();
- if (targetCachedDevice.getGroupId() == fallbackActiveGroupId) {
+ if (getGroupId(targetCachedDevice) == fallbackActiveGroupId) {
Log.d(
TAG,
"Skip updateFallbackActiveDeviceIfNeeded, already is fallback: "
@@ -1091,6 +1093,23 @@
BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
}
+ private int getGroupId(CachedBluetoothDevice cachedDevice) {
+ int groupId = cachedDevice.getGroupId();
+ String anonymizedAddress = cachedDevice.getDevice().getAnonymizedAddress();
+ if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
+ Log.d(TAG, "getGroupId by CSIP profile for device: " + anonymizedAddress);
+ return groupId;
+ }
+ for (LocalBluetoothProfile profile : cachedDevice.getProfiles()) {
+ if (profile instanceof LeAudioProfile) {
+ Log.d(TAG, "getGroupId by LEA profile for device: " + anonymizedAddress);
+ return ((LeAudioProfile) profile).getGroupId(cachedDevice.getDevice());
+ }
+ }
+ Log.d(TAG, "getGroupId return invalid id for device: " + anonymizedAddress);
+ return BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
+ }
+
private void notifyBroadcastStateChange(@BroadcastState int state) {
if (!mContext.getPackageName().equals(SETTINGS_PKG)) {
Log.d(TAG, "Skip notifyBroadcastStateChange, not triggered by Settings.");
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
index 840c936..b7108c9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
@@ -236,7 +236,8 @@
// Handle specific carrier config values for the default data SIM
int defaultDataSubId = SubscriptionManager.from(context)
.getDefaultDataSubscriptionId();
- PersistableBundle b = configMgr.getConfigForSubId(defaultDataSubId);
+ PersistableBundle b = configMgr == null ? null
+ : configMgr.getConfigForSubId(defaultDataSubId);
if (b != null) {
config.alwaysShowDataRatIcon = b.getBoolean(
CarrierConfigManager.KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL);
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
index 53daef1..69c7410 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
@@ -242,6 +242,7 @@
.setMessage(messageResId)
.setNegativeButtonText(R.string.cancel)
.setPositiveButtonText(R.string.next);
+ mCustomDialogHelper.requestFocusOnTitle();
break;
case GRANT_ADMIN_DIALOG:
mEditUserInfoView.setVisibility(View.GONE);
@@ -254,6 +255,7 @@
.setMessage(R.string.user_grant_admin_message)
.setNegativeButtonText(R.string.back)
.setPositiveButtonText(R.string.next);
+ mCustomDialogHelper.requestFocusOnTitle();
if (mIsAdmin == null) {
mCustomDialogHelper.setButtonEnabled(false);
}
@@ -265,6 +267,7 @@
.setTitle(R.string.user_info_settings_title)
.setNegativeButtonText(R.string.back)
.setPositiveButtonText(R.string.done);
+ mCustomDialogHelper.requestFocusOnTitle();
mEditUserInfoView.setVisibility(View.VISIBLE);
mGrantAdminView.setVisibility(View.GONE);
break;
@@ -273,7 +276,6 @@
&& mEditUserPhotoController.getNewUserPhotoDrawable() != null)
? mEditUserPhotoController.getNewUserPhotoDrawable()
: mSavedDrawable;
-
String newName = mUserNameView.getText().toString().trim();
String defaultName = mActivity.getString(R.string.user_new_user_name);
mUserName = !newName.isEmpty() ? newName : defaultName;
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java b/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java
index 5201b3d..4cf3bc2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java
@@ -23,6 +23,7 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -282,4 +283,13 @@
}
return this;
}
+
+ /**
+ * Requests focus on dialog title when used. Used to let talkback know that the dialog content
+ * is updated and needs to be read from the beginning.
+ */
+ public void requestFocusOnTitle() {
+ mDialogTitle.requestFocus();
+ mDialogTitle.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
index 56b0bf7..1586b8f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
@@ -23,8 +23,8 @@
import com.android.settingslib.volume.shared.model.AudioStreamModel
import com.android.settingslib.volume.shared.model.RingerMode
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
/** Provides audio stream state and an ability to change it */
@@ -43,6 +43,9 @@
streamModel.copy(volume = processVolume(streamModel, ringerMode, isZenMuted))
}
+ val ringerMode: StateFlow<RingerMode>
+ get() = audioRepository.ringerMode
+
suspend fun setVolume(audioStream: AudioStream, volume: Int) =
audioRepository.setVolume(audioStream, volume)
@@ -52,9 +55,14 @@
/** Checks if the volume can be changed via the UI. */
fun canChangeVolume(audioStream: AudioStream): Flow<Boolean> {
return if (audioStream.value == AudioManager.STREAM_NOTIFICATION) {
- getAudioStream(AudioStream(AudioManager.STREAM_RING)).map { !it.isMuted }
+ combine(
+ notificationsSoundPolicyInteractor.isZenMuted(audioStream),
+ getAudioStream(AudioStream(AudioManager.STREAM_RING)).map { it.isMuted },
+ ) { isZenMuted, isRingMuted ->
+ !isZenMuted && !isRingMuted
+ }
} else {
- flowOf(true)
+ notificationsSoundPolicyInteractor.isZenMuted(audioStream).map { !it }
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
index fe1529d..9c518de 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
@@ -192,7 +192,7 @@
mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
final Executor executor = (command -> new Thread(command).start());
- final BluetoothVolumeControl.Callback callback = (device, volumeOffset) -> {};
+ final BluetoothVolumeControl.Callback callback = new BluetoothVolumeControl.Callback() {};
mProfile.registerCallback(executor, callback);
verify(mService).registerCallback(executor, callback);
@@ -200,7 +200,7 @@
@Test
public void unregisterCallback_verifyIsCalled() {
- final BluetoothVolumeControl.Callback callback = (device, volumeOffset) -> {};
+ final BluetoothVolumeControl.Callback callback = new BluetoothVolumeControl.Callback() {};
mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
mProfile.unregisterCallback(callback);
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 30d5d4b..eaec617 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -269,6 +269,7 @@
Settings.Secure.STYLUS_POINTER_ICON_ENABLED,
Settings.Secure.CAMERA_EXTENSIONS_FALLBACK,
Settings.Secure.VISUAL_QUERY_ACCESSIBILITY_DETECTION_ENABLED,
- Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS
+ Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
+ Settings.Secure.AUDIO_DEVICE_INVENTORY
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 893932f..046d6e2 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -424,5 +424,6 @@
VALIDATORS.put(Secure.STYLUS_POINTER_ICON_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.CAMERA_EXTENSIONS_FALLBACK, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.IMMERSIVE_MODE_CONFIRMATIONS, ANY_STRING_VALIDATOR);
+ VALIDATORS.put(Secure.AUDIO_DEVICE_INVENTORY, ANY_STRING_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 2a8eb9b..3266c12 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -707,23 +707,36 @@
// Update/add new keys
for (String key : keyValues.keySet()) {
String value = keyValues.get(key);
+
+ // Rename key if it's an aconfig flag.
+ String flagName = key;
+ if (Flags.stageAllAconfigFlags() && isConfigSettingsKey(mKey)) {
+ int slashIndex = flagName.indexOf("/");
+ boolean stageFlag = slashIndex > 0 && slashIndex != flagName.length();
+ boolean isAconfig = trunkFlagMap != null && trunkFlagMap.containsKey(flagName);
+ if (stageFlag && isAconfig) {
+ String flagWithoutNamespace = flagName.substring(slashIndex + 1);
+ flagName = "staged/" + namespace + "*" + flagWithoutNamespace;
+ }
+ }
+
String oldValue = null;
- Setting state = mSettings.get(key);
+ Setting state = mSettings.get(flagName);
if (state == null) {
- state = new Setting(key, value, false, packageName, null);
- mSettings.put(key, state);
- changedKeys.add(key); // key was added
+ state = new Setting(flagName, value, false, packageName, null);
+ mSettings.put(flagName, state);
+ changedKeys.add(flagName); // key was added
} else if (state.value != value) {
oldValue = state.value;
state.update(value, false, packageName, null, true,
/* overrideableByRestore */ false);
- changedKeys.add(key); // key was updated
+ changedKeys.add(flagName); // key was updated
} else {
// this key/value already exists, no change and no logging necessary
continue;
}
- FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, key, value, state.value,
+ FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, flagName, value, state.value,
oldValue, /* tag */ null, /* make default */ false,
getUserIdFromKey(mKey), FrameworkStatsLog.SETTING_CHANGED__REASON__UPDATED);
addHistoricalOperationLocked(HISTORICAL_OPERATION_UPDATE, state);
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 46c89900..6eb2dd0 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -670,7 +670,6 @@
Settings.Secure.AUTOMATIC_STORAGE_MANAGER_ENABLED,
Settings.Secure.AUTOMATIC_STORAGE_MANAGER_LAST_RUN,
Settings.Secure.AUTOMATIC_STORAGE_MANAGER_TURNED_OFF_BY_POLICY,
- Settings.Secure.AUDIO_DEVICE_INVENTORY, // not controllable by user
Settings.Secure.AUDIO_SAFE_CSD_AS_A_FEATURE_ENABLED, // not controllable by user
Settings.Secure.BACKUP_AUTO_RESTORE,
Settings.Secure.BACKUP_ENABLED,
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 33362a2..e0e31d7 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -740,6 +740,51 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_STAGE_ALL_ACONFIG_FLAGS)
+ public void testSetSettingsLockedStagesAconfigFlags() throws Exception {
+ int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
+
+ SettingsState settingsState = new SettingsState(
+ InstrumentationRegistry.getContext(), mLock, mSettingsFile, configKey,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+
+ String prefix = "test_namespace";
+ String packageName = "com.android.flags";
+ Map<String, String> keyValues =
+ Map.of("test_namespace/com.android.flags.flag3", "true");
+
+ parsed_flags flags = parsed_flags
+ .newBuilder()
+ .addParsedFlag(parsed_flag
+ .newBuilder()
+ .setPackage(packageName)
+ .setName("flag3")
+ .setNamespace(prefix)
+ .setDescription("test flag")
+ .addBug("12345678")
+ .setState(Aconfig.flag_state.DISABLED)
+ .setPermission(Aconfig.flag_permission.READ_WRITE))
+ .build();
+
+ synchronized (mLock) {
+ settingsState.loadAconfigDefaultValues(
+ flags.toByteArray(), settingsState.getAconfigDefaultValues());
+ List<String> updates =
+ settingsState.setSettingsLocked("test_namespace/", keyValues, packageName);
+ assertEquals(1, updates.size());
+ assertEquals(updates.get(0), "staged/test_namespace*com.android.flags.flag3");
+
+ SettingsState.Setting s;
+
+ s = settingsState.getSettingLocked("test_namespace/com.android.flags.flag3");
+ assertNull(s.getValue());
+
+ s = settingsState.getSettingLocked("staged/test_namespace*com.android.flags.flag3");
+ assertEquals("true", s.getValue());
+ }
+ }
+
+ @Test
public void testsetSettingsLockedKeepTrunkDefault() throws Exception {
final PrintStream os = new PrintStream(new FileOutputStream(mSettingsFile));
os.print(
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index eb2d13d..43ea3ec 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -698,6 +698,9 @@
<!-- Permission required for CTS test - CtsWearableSensingServiceTestCases -->
<uses-permission android:name="android.permission.MANAGE_WEARABLE_SENSING_SERVICE" />
+ <!-- Permission required for CTS test - OnDeviceIntelligenceManagerTest -->
+ <uses-permission android:name="android.permission.USE_ON_DEVICE_INTELLIGENCE" />
+
<!-- Permission required for CTS test - CallAudioInterceptionTest -->
<uses-permission android:name="android.permission.CALL_AUDIO_INTERCEPTION" />
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 98591e9..e62c77dc 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -179,6 +179,7 @@
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT"/>
<uses-permission android:name="android.permission.USE_EXACT_ALARM"/>
+ <uses-permission android:name="android.permission.RECORD_SENSITIVE_CONTENT"/>
<!-- Assist -->
<uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" />
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index da06830..1c98630 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -115,10 +115,10 @@
}
flag {
- name: "notifications_background_media_icons"
+ name: "notifications_background_icons"
namespace: "systemui"
- description: "Updates icons for media notifications in the background."
- bug: "315143160"
+ description: "Moves part of the notification icon updates to the background."
+ bug: "315143361"
metadata {
purpose: PURPOSE_BUGFIX
}
@@ -463,6 +463,13 @@
}
flag {
+ name: "enable_contextual_tips_frequency_cap"
+ description: "Enables frequency capping for contextual tips, e.g. 1x/day, 2x/week, 3x/lifetime."
+ namespace: "systemui"
+ bug: "322891421"
+}
+
+flag {
name: "enable_contextual_tips"
description: "Enables showing contextual tips."
namespace: "systemui"
@@ -617,3 +624,14 @@
description: "enables new focus outline for qs tiles when focused on with physical keyboard"
bug: "312899524"
}
+
+flag {
+ name: "edgeback_gesture_handler_get_running_tasks_background"
+ namespace: "systemui"
+ description: "Decide whether to get the running tasks from activity manager in EdgebackGestureHandler"
+ " class on the background thread."
+ bug: "325041960"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
index 5d5f12e..3f57f88 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
@@ -337,6 +337,7 @@
if (ghostedView is LaunchableView) {
// Restore the ghosted view visibility.
ghostedView.setShouldBlockVisibilityChanges(false)
+ ghostedView.onActivityLaunchAnimationEnd()
} else {
// Make the ghosted view visible. We ensure that the view is considered VISIBLE by
// accessibility by first making it INVISIBLE then VISIBLE (see b/204944038#comment17
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
index ed8e705..da6ccaa 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
@@ -38,6 +38,9 @@
* @param block whether we should block/postpone all calls to `setVisibility`.
*/
fun setShouldBlockVisibilityChanges(block: Boolean)
+
+ /** Perform an action when the activity launch animation ends */
+ fun onActivityLaunchAnimationEnd() {}
}
/** A delegate that can be used by views to make the implementation of [LaunchableView] easier. */
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt b/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
index ef15c84..9a99649 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
@@ -21,22 +21,25 @@
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.DragInteraction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.aspectRatio
-import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderState
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -44,17 +47,28 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.RoundRect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.clipPath
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasurePolicy
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
-import com.android.compose.modifiers.padding
+import androidx.compose.ui.util.fastFirst
+import androidx.compose.ui.util.fastFirstOrNull
import com.android.compose.theme.LocalAndroidColorScheme
/**
@@ -62,15 +76,16 @@
*
* @param onValueChangeFinished is called when the slider settles on a [value]. This callback
* shouldn't be used to react to value changes. Use [onValueChange] instead
- * @param interactionSource - the [MutableInteractionSource] representing the stream of Interactions
+ * @param interactionSource the [MutableInteractionSource] representing the stream of Interactions
* for this slider. You can create and pass in your own remembered instance to observe
* Interactions and customize the appearance / behavior of this slider in different states.
- * @param colors - slider color scheme.
- * @param draggingCornersRadius - radius of the slider indicator when the user drags it
- * @param icon - icon at the start of the slider. Icon is limited to a square space at the start of
- * the slider
- * @param label - control shown next to the icon.
+ * @param colors determine slider color scheme.
+ * @param draggingCornersRadius is the radius of the slider indicator when the user drags it
+ * @param icon at the start of the slider. Icon is limited to a square space at the start of the
+ * slider
+ * @param label is shown next to the icon.
*/
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PlatformSlider(
value: Float,
@@ -86,7 +101,7 @@
label: (@Composable (isDragging: Boolean) -> Unit)? = null,
) {
val sliderHeight: Dp = 64.dp
- val iconWidth: Dp = sliderHeight
+ val thumbSize: Dp = sliderHeight
var isDragging by remember { mutableStateOf(false) }
LaunchedEffect(interactionSource) {
interactionSource.interactions.collect { interaction ->
@@ -101,16 +116,6 @@
}
}
}
- val paddingStart by
- animateDpAsState(
- targetValue =
- if ((!isDragging && value == valueRange.start) || icon == null) {
- 16.dp
- } else {
- 0.dp
- },
- label = "LabelIconSpacingAnimation"
- )
Box(modifier = modifier.height(sliderHeight)) {
Slider(
@@ -126,130 +131,275 @@
sliderState = it,
enabled = enabled,
colors = colors,
- iconWidth = iconWidth,
draggingCornersRadius = draggingCornersRadius,
sliderHeight = sliderHeight,
+ thumbSize = thumbSize,
isDragging = isDragging,
- modifier = Modifier,
+ label = label,
+ icon = icon,
+ modifier = Modifier.fillMaxSize(),
)
},
- thumb = { Spacer(Modifier.width(iconWidth).height(sliderHeight)) },
+ thumb = { Spacer(Modifier.size(thumbSize)) },
)
- if (icon != null || label != null) {
- Row(modifier = Modifier.fillMaxSize()) {
- icon?.let { iconComposable ->
- Box(
- modifier = Modifier.fillMaxHeight().aspectRatio(1f),
- contentAlignment = Alignment.Center,
- ) {
- iconComposable(isDragging)
- }
- }
-
- label?.let { labelComposable ->
- Box(
- modifier =
- Modifier.fillMaxHeight()
- .weight(1f)
- .padding(
- start = { paddingStart.roundToPx() },
- end = { sliderHeight.roundToPx() / 2 },
- ),
- contentAlignment = Alignment.CenterStart,
- ) {
- labelComposable(isDragging)
- }
- }
- }
- }
+ Spacer(
+ Modifier.padding(8.dp)
+ .size(4.dp)
+ .align(Alignment.CenterEnd)
+ .background(color = colors.indicatorColor, shape = CircleShape)
+ )
}
}
+private enum class TrackComponent(val zIndex: Float) {
+ Background(0f),
+ Icon(1f),
+ Label(1f),
+}
+
@Composable
private fun Track(
sliderState: SliderState,
enabled: Boolean,
colors: PlatformSliderColors,
- iconWidth: Dp,
draggingCornersRadius: Dp,
sliderHeight: Dp,
+ thumbSize: Dp,
isDragging: Boolean,
+ icon: (@Composable (isDragging: Boolean) -> Unit)?,
+ label: (@Composable (isDragging: Boolean) -> Unit)?,
modifier: Modifier = Modifier,
) {
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
- val iconWidthPx: Float
- val halfIconWidthPx: Float
- val targetIndicatorRadiusPx: Float
- val halfSliderHeightPx: Float
- with(LocalDensity.current) {
- halfSliderHeightPx = sliderHeight.toPx() / 2
- iconWidthPx = iconWidth.toPx()
- halfIconWidthPx = iconWidthPx / 2
- targetIndicatorRadiusPx =
- if (isDragging) draggingCornersRadius.toPx() else halfSliderHeightPx
- }
+ var drawingState: DrawingState by remember { mutableStateOf(DrawingState()) }
+ Layout(
+ modifier = modifier,
+ content = {
+ TrackBackground(
+ modifier = Modifier.layoutId(TrackComponent.Background),
+ drawingState = drawingState,
+ enabled = enabled,
+ colors = colors,
+ draggingCornersRadiusActive = draggingCornersRadius,
+ draggingCornersRadiusIdle = sliderHeight / 2,
+ isDragging = isDragging,
+ )
+ if (icon != null) {
+ Box(
+ modifier = Modifier.layoutId(TrackComponent.Icon).clip(CircleShape),
+ contentAlignment = Alignment.Center,
+ ) {
+ CompositionLocalProvider(
+ LocalContentColor provides
+ if (enabled) colors.iconColor else colors.disabledIconColor
+ ) {
+ icon(isDragging)
+ }
+ }
+ }
+ if (label != null) {
+ val offsetX by
+ animateFloatAsState(
+ targetValue =
+ if (enabled) {
+ if (drawingState.isLabelOnTopOfIndicator) {
+ drawingState.iconWidth.coerceAtLeast(
+ LocalDensity.current.run { 16.dp.toPx() }
+ )
+ } else {
+ val indicatorWidth =
+ drawingState.indicatorRight - drawingState.indicatorLeft
+ indicatorWidth + LocalDensity.current.run { 16.dp.toPx() }
+ }
+ } else {
+ drawingState.iconWidth
+ },
+ label = "LabelIconSpacingAnimation"
+ )
+ Box(
+ modifier =
+ Modifier.layoutId(TrackComponent.Label).offset {
+ IntOffset(offsetX.toInt(), 0)
+ },
+ contentAlignment = Alignment.CenterStart,
+ ) {
+ CompositionLocalProvider(
+ LocalContentColor provides
+ colors.getLabelColor(
+ isEnabled = enabled,
+ isLabelOnTopOfTheIndicator = drawingState.isLabelOnTopOfIndicator,
+ )
+ ) {
+ label(isDragging)
+ }
+ }
+ }
+ },
+ measurePolicy =
+ TrackMeasurePolicy(
+ sliderState = sliderState,
+ thumbSize = LocalDensity.current.run { thumbSize.roundToPx() },
+ isRtl = isRtl,
+ onDrawingStateMeasured = { drawingState = it }
+ )
+ )
+}
- val indicatorRadiusPx: Float by
- animateFloatAsState(
- targetValue = targetIndicatorRadiusPx,
+@Composable
+private fun TrackBackground(
+ drawingState: DrawingState,
+ enabled: Boolean,
+ colors: PlatformSliderColors,
+ draggingCornersRadiusActive: Dp,
+ draggingCornersRadiusIdle: Dp,
+ isDragging: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ val indicatorRadiusDp: Dp by
+ animateDpAsState(
+ targetValue =
+ if (isDragging) draggingCornersRadiusActive else draggingCornersRadiusIdle,
label = "PlatformSliderCornersAnimation",
)
val trackColor = colors.getTrackColor(enabled)
val indicatorColor = colors.getIndicatorColor(enabled)
- val trackCornerRadius = CornerRadius(halfSliderHeightPx, halfSliderHeightPx)
- val indicatorCornerRadius = CornerRadius(indicatorRadiusPx, indicatorRadiusPx)
Canvas(modifier.fillMaxSize()) {
+ val trackCornerRadius = CornerRadius(size.height / 2, size.height / 2)
val trackPath = Path()
trackPath.addRoundRect(
RoundRect(
- left = -halfIconWidthPx,
+ left = 0f,
top = 0f,
- right = size.width + halfIconWidthPx,
- bottom = size.height,
+ right = drawingState.totalWidth,
+ bottom = drawingState.totalHeight,
cornerRadius = trackCornerRadius,
)
)
drawPath(path = trackPath, color = trackColor)
+ val indicatorCornerRadius = CornerRadius(indicatorRadiusDp.toPx(), indicatorRadiusDp.toPx())
clipPath(trackPath) {
val indicatorPath = Path()
- if (isRtl) {
- indicatorPath.addRoundRect(
- RoundRect(
- left =
- size.width -
- size.width * sliderState.coercedNormalizedValue -
- halfIconWidthPx,
- top = 0f,
- right = size.width + iconWidthPx,
- bottom = size.height,
- topLeftCornerRadius = indicatorCornerRadius,
- topRightCornerRadius = trackCornerRadius,
- bottomRightCornerRadius = trackCornerRadius,
- bottomLeftCornerRadius = indicatorCornerRadius,
- )
+ indicatorPath.addRoundRect(
+ RoundRect(
+ left = drawingState.indicatorLeft,
+ top = drawingState.indicatorTop,
+ right = drawingState.indicatorRight,
+ bottom = drawingState.indicatorBottom,
+ topLeftCornerRadius = trackCornerRadius,
+ topRightCornerRadius = indicatorCornerRadius,
+ bottomRightCornerRadius = indicatorCornerRadius,
+ bottomLeftCornerRadius = trackCornerRadius,
)
- } else {
- indicatorPath.addRoundRect(
- RoundRect(
- left = -halfIconWidthPx,
- top = 0f,
- right = size.width * sliderState.coercedNormalizedValue + halfIconWidthPx,
- bottom = size.height,
- topLeftCornerRadius = trackCornerRadius,
- topRightCornerRadius = indicatorCornerRadius,
- bottomRightCornerRadius = indicatorCornerRadius,
- bottomLeftCornerRadius = trackCornerRadius,
- )
- )
- }
+ )
drawPath(path = indicatorPath, color = indicatorColor)
}
}
}
+/** Measures track components sizes and calls [onDrawingStateMeasured] when it's done. */
+private class TrackMeasurePolicy(
+ private val sliderState: SliderState,
+ private val thumbSize: Int,
+ private val isRtl: Boolean,
+ private val onDrawingStateMeasured: (DrawingState) -> Unit,
+) : MeasurePolicy {
+
+ override fun MeasureScope.measure(
+ measurables: List<Measurable>,
+ constraints: Constraints
+ ): MeasureResult {
+ // Slider adds a paddings to the Track to make spase for thumb
+ val desiredWidth = constraints.maxWidth + thumbSize
+ val desiredHeight = constraints.maxHeight
+ val backgroundPlaceable: Placeable =
+ measurables
+ .fastFirst { it.layoutId == TrackComponent.Background }
+ .measure(Constraints(desiredWidth, desiredWidth, desiredHeight, desiredHeight))
+
+ val iconPlaceable: Placeable? =
+ measurables
+ .fastFirstOrNull { it.layoutId == TrackComponent.Icon }
+ ?.measure(
+ Constraints(
+ minWidth = desiredHeight,
+ maxWidth = desiredHeight,
+ minHeight = desiredHeight,
+ maxHeight = desiredHeight,
+ )
+ )
+
+ val iconSize = iconPlaceable?.width ?: 0
+ val labelMaxWidth = (desiredWidth - iconSize) / 2
+ val labelPlaceable: Placeable? =
+ measurables
+ .fastFirstOrNull { it.layoutId == TrackComponent.Label }
+ ?.measure(
+ Constraints(
+ minWidth = 0,
+ maxWidth = labelMaxWidth,
+ minHeight = desiredHeight,
+ maxHeight = desiredHeight,
+ )
+ )
+
+ val drawingState =
+ if (isRtl) {
+ DrawingState(
+ isRtl = true,
+ totalWidth = desiredWidth.toFloat(),
+ totalHeight = desiredHeight.toFloat(),
+ indicatorLeft =
+ (desiredWidth - iconSize) * (1 - sliderState.coercedNormalizedValue),
+ indicatorTop = 0f,
+ indicatorRight = desiredWidth.toFloat(),
+ indicatorBottom = desiredHeight.toFloat(),
+ iconWidth = iconSize.toFloat(),
+ labelWidth = labelPlaceable?.width?.toFloat() ?: 0f,
+ )
+ } else {
+ DrawingState(
+ isRtl = false,
+ totalWidth = desiredWidth.toFloat(),
+ totalHeight = desiredHeight.toFloat(),
+ indicatorLeft = 0f,
+ indicatorTop = 0f,
+ indicatorRight =
+ iconSize + (desiredWidth - iconSize) * sliderState.coercedNormalizedValue,
+ indicatorBottom = desiredHeight.toFloat(),
+ iconWidth = iconSize.toFloat(),
+ labelWidth = labelPlaceable?.width?.toFloat() ?: 0f,
+ )
+ }
+
+ onDrawingStateMeasured(drawingState)
+
+ return layout(desiredWidth, desiredHeight) {
+ backgroundPlaceable.placeRelative(0, 0, TrackComponent.Background.zIndex)
+
+ iconPlaceable?.placeRelative(0, 0, TrackComponent.Icon.zIndex)
+ labelPlaceable?.placeRelative(0, 0, TrackComponent.Label.zIndex)
+ }
+ }
+}
+
+private data class DrawingState(
+ val isRtl: Boolean = false,
+ val totalWidth: Float = 0f,
+ val totalHeight: Float = 0f,
+ val indicatorLeft: Float = 0f,
+ val indicatorTop: Float = 0f,
+ val indicatorRight: Float = 0f,
+ val indicatorBottom: Float = 0f,
+ val iconWidth: Float = 0f,
+ val labelWidth: Float = 0f,
+)
+
+private val DrawingState.isLabelOnTopOfIndicator: Boolean
+ get() = labelWidth < indicatorRight - indicatorLeft - iconWidth
+
/** [SliderState.value] normalized using [SliderState.valueRange]. The result belongs to [0, 1] */
private val SliderState.coercedNormalizedValue: Float
get() {
@@ -268,17 +418,19 @@
* @param trackColor fills the track of the slider. This is a "background" of the slider
* @param indicatorColor fills the slider from the start to the value
* @param iconColor is the default icon color
- * @param labelColor is the default icon color
+ * @param labelColorOnIndicator is the label color for when it's shown on top of the indicator
+ * @param labelColorOnTrack is the label color for when it's shown on top of the track
* @param disabledTrackColor is the [trackColor] when the PlatformSlider#enabled == false
* @param disabledIndicatorColor is the [indicatorColor] when the PlatformSlider#enabled == false
* @param disabledIconColor is the [iconColor] when the PlatformSlider#enabled == false
- * @param disabledLabelColor is the [labelColor] when the PlatformSlider#enabled == false
+ * @param disabledLabelColor is the label color when the PlatformSlider#enabled == false
*/
data class PlatformSliderColors(
val trackColor: Color,
val indicatorColor: Color,
val iconColor: Color,
- val labelColor: Color,
+ val labelColorOnIndicator: Color,
+ val labelColorOnTrack: Color,
val disabledTrackColor: Color,
val disabledIndicatorColor: Color,
val disabledIconColor: Color,
@@ -300,10 +452,11 @@
@Composable
private fun lightThemePlatformSliderColors() =
PlatformSliderColors(
- trackColor = MaterialTheme.colorScheme.tertiaryContainer,
- indicatorColor = LocalAndroidColorScheme.current.tertiaryFixedDim,
- iconColor = MaterialTheme.colorScheme.onTertiaryContainer,
- labelColor = MaterialTheme.colorScheme.onTertiaryContainer,
+ trackColor = LocalAndroidColorScheme.current.tertiaryFixedDim,
+ indicatorColor = MaterialTheme.colorScheme.tertiary,
+ iconColor = MaterialTheme.colorScheme.onTertiary,
+ labelColorOnIndicator = MaterialTheme.colorScheme.onTertiary,
+ labelColorOnTrack = LocalAndroidColorScheme.current.onTertiaryFixed,
disabledTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
disabledIndicatorColor = MaterialTheme.colorScheme.surfaceContainerHighest,
disabledIconColor = MaterialTheme.colorScheme.outline,
@@ -314,10 +467,11 @@
@Composable
private fun darkThemePlatformSliderColors() =
PlatformSliderColors(
- trackColor = MaterialTheme.colorScheme.onTertiary,
- indicatorColor = LocalAndroidColorScheme.current.onTertiaryFixedVariant,
+ trackColor = MaterialTheme.colorScheme.tertiary,
+ indicatorColor = MaterialTheme.colorScheme.tertiary,
iconColor = MaterialTheme.colorScheme.onTertiaryContainer,
- labelColor = MaterialTheme.colorScheme.onTertiaryContainer,
+ labelColorOnIndicator = MaterialTheme.colorScheme.onTertiary,
+ labelColorOnTrack = LocalAndroidColorScheme.current.onTertiaryFixed,
disabledTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
disabledIndicatorColor = MaterialTheme.colorScheme.surfaceContainerHighest,
disabledIconColor = MaterialTheme.colorScheme.outline,
@@ -329,3 +483,14 @@
private fun PlatformSliderColors.getIndicatorColor(isEnabled: Boolean): Color =
if (isEnabled) indicatorColor else disabledIndicatorColor
+
+private fun PlatformSliderColors.getLabelColor(
+ isEnabled: Boolean,
+ isLabelOnTopOfTheIndicator: Boolean
+): Color {
+ return if (isEnabled) {
+ if (isLabelOnTopOfTheIndicator) labelColorOnIndicator else labelColorOnTrack
+ } else {
+ disabledLabelColor
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 9ee69bc..d0c4984 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -24,10 +24,10 @@
import com.android.compose.animation.scene.updateSceneTransitionLayoutState
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.communal.ui.compose.extensions.allowGestures
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
object Communal {
object Elements {
@@ -63,6 +63,7 @@
fun CommunalContainer(
modifier: Modifier = Modifier,
viewModel: CommunalViewModel,
+ dialogFactory: SystemUIDialogFactory,
) {
val currentScene: SceneKey by viewModel.currentScene.collectAsState(CommunalScenes.Blank)
val sceneTransitionLayoutState =
@@ -82,7 +83,7 @@
SceneTransitionLayout(
state = sceneTransitionLayoutState,
- modifier = modifier.fillMaxSize().allowGestures(allowed = touchesAllowed),
+ modifier = modifier.fillMaxSize(),
swipeSourceDetector =
FixedSizeEdgeDetector(
dimensionResource(id = R.dimen.communal_gesture_initiation_width)
@@ -91,9 +92,14 @@
scene(
CommunalScenes.Blank,
userActions =
- mapOf(
- Swipe(SwipeDirection.Left, fromSource = Edge.Right) to CommunalScenes.Communal
- )
+ if (touchesAllowed) {
+ mapOf(
+ Swipe(SwipeDirection.Left, fromSource = Edge.Right) to
+ CommunalScenes.Communal
+ )
+ } else {
+ emptyMap()
+ }
) {
// This scene shows nothing only allowing for transitions to the communal scene.
Box(modifier = Modifier.fillMaxSize())
@@ -102,9 +108,15 @@
scene(
CommunalScenes.Communal,
userActions =
- mapOf(Swipe(SwipeDirection.Right, fromSource = Edge.Left) to CommunalScenes.Blank),
+ if (touchesAllowed) {
+ mapOf(
+ Swipe(SwipeDirection.Right, fromSource = Edge.Left) to CommunalScenes.Blank
+ )
+ } else {
+ emptyMap()
+ },
) {
- CommunalScene(viewModel, modifier = modifier)
+ CommunalScene(viewModel, dialogFactory, modifier = modifier)
}
}
}
@@ -113,6 +125,7 @@
@Composable
private fun SceneScope.CommunalScene(
viewModel: BaseCommunalViewModel,
+ dialogFactory: SystemUIDialogFactory,
modifier: Modifier = Modifier,
) {
Box(
@@ -121,5 +134,7 @@
.fillMaxSize()
.background(LocalAndroidColorScheme.current.outlineVariant),
)
- Box(modifier.element(Communal.Elements.Content)) { CommunalHub(viewModel = viewModel) }
+ Box(modifier.element(Communal.Elements.Content)) {
+ CommunalHub(viewModel = viewModel, dialogFactory = dialogFactory)
+ }
}
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 3050f7a..0d6b710 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
@@ -29,6 +29,7 @@
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
@@ -118,12 +119,13 @@
import com.android.systemui.communal.ui.compose.extensions.allowGestures
import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture
import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
-import com.android.systemui.communal.ui.compose.extensions.observeTapsWithoutConsuming
+import com.android.systemui.communal.ui.compose.extensions.observeTaps
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import kotlinx.coroutines.launch
@OptIn(ExperimentalComposeUiApi::class)
@@ -131,6 +133,7 @@
fun CommunalHub(
modifier: Modifier = Modifier,
viewModel: BaseCommunalViewModel,
+ dialogFactory: SystemUIDialogFactory? = null,
widgetConfigurator: WidgetConfigurator? = null,
onOpenWidgetPicker: (() -> Unit)? = null,
onEditDone: (() -> Unit)? = null,
@@ -165,7 +168,7 @@
.pointerInput(gridState, contentOffset, contentListState) {
// If not in edit mode, don't allow selecting items.
if (!viewModel.isEditMode) return@pointerInput
- observeTapsWithoutConsuming { offset ->
+ observeTaps { offset ->
val adjustedOffset = offset - contentOffset
val index = firstIndexAtOffset(gridState, adjustedOffset)
val key = index?.let { keyAtIndexIfEditable(contentListState.list, index) }
@@ -271,6 +274,31 @@
)
}
+ if (viewModel is CommunalViewModel && dialogFactory != null) {
+ val isEnableWidgetDialogShowing by
+ viewModel.isEnableWidgetDialogShowing.collectAsState(false)
+ val isEnableWorkProfileDialogShowing by
+ viewModel.isEnableWorkProfileDialogShowing.collectAsState(false)
+
+ EnableWidgetDialog(
+ isEnableWidgetDialogVisible = isEnableWidgetDialogShowing,
+ dialogFactory = dialogFactory,
+ title = stringResource(id = R.string.dialog_title_to_allow_any_widget),
+ positiveButtonText = stringResource(id = R.string.button_text_to_open_settings),
+ onConfirm = viewModel::onEnableWidgetDialogConfirm,
+ onCancel = viewModel::onEnableWidgetDialogCancel
+ )
+
+ EnableWidgetDialog(
+ isEnableWidgetDialogVisible = isEnableWorkProfileDialogShowing,
+ dialogFactory = dialogFactory,
+ title = stringResource(id = R.string.work_mode_off_title),
+ positiveButtonText = stringResource(id = R.string.work_mode_turn_on),
+ onConfirm = viewModel::onEnableWorkProfileDialogConfirm,
+ onCancel = viewModel::onEnableWorkProfileDialogCancel
+ )
+ }
+
// This spacer covers the edge of the LazyHorizontalGrid and prevents it from receiving
// touches, so that the SceneTransitionLayout can intercept the touches and allow an edge
// swipe back to the blank scene.
@@ -616,7 +644,7 @@
WidgetContent(viewModel, model, size, selected, widgetConfigurator, modifier)
is CommunalContentModel.WidgetPlaceholder -> HighlightedItem(modifier)
is CommunalContentModel.WidgetContent.DisabledWidget ->
- DisabledWidgetPlaceholder(model, modifier)
+ DisabledWidgetPlaceholder(model, viewModel, modifier)
is CommunalContentModel.CtaTileInViewMode -> CtaTileInViewModeContent(viewModel, modifier)
is CommunalContentModel.CtaTileInEditMode ->
CtaTileInEditModeContent(modifier, onOpenWidgetPicker)
@@ -754,7 +782,16 @@
modifier: Modifier = Modifier,
) {
Box(
- modifier = modifier,
+ modifier =
+ modifier.thenIf(!viewModel.isEditMode && model.inQuietMode) {
+ Modifier.pointerInput(Unit) {
+ // consume tap to prevent the child view from triggering interactions with the
+ // app widget
+ observeTaps(shouldConsume = true) { _ ->
+ viewModel.onOpenEnableWorkProfileDialog()
+ }
+ }
+ }
) {
AndroidView(
modifier = Modifier.fillMaxSize().allowGestures(allowed = !viewModel.isEditMode),
@@ -826,6 +863,7 @@
@Composable
fun DisabledWidgetPlaceholder(
model: CommunalContentModel.WidgetContent.DisabledWidget,
+ viewModel: BaseCommunalViewModel,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
@@ -839,10 +877,17 @@
Column(
modifier =
- modifier.background(
- MaterialTheme.colorScheme.surfaceVariant,
- RoundedCornerShape(dimensionResource(system_app_widget_background_radius))
- ),
+ modifier
+ .background(
+ MaterialTheme.colorScheme.surfaceVariant,
+ RoundedCornerShape(dimensionResource(system_app_widget_background_radius))
+ )
+ .clickable(
+ enabled = !viewModel.isEditMode,
+ interactionSource = null,
+ indication = null,
+ onClick = viewModel::onOpenEnableWidgetDialog
+ ),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
index 3d88ad5..9e905ac 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
@@ -27,6 +27,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -38,6 +39,7 @@
@Inject
constructor(
private val viewModel: CommunalViewModel,
+ private val dialogFactory: SystemUIDialogFactory,
) : ComposableScene {
override val key = Scenes.Communal
@@ -51,6 +53,6 @@
@Composable
override fun SceneScope.Content(modifier: Modifier) {
- CommunalHub(modifier, viewModel)
+ CommunalHub(modifier, viewModel, dialogFactory)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/EnableWidgetDialog.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/EnableWidgetDialog.kt
new file mode 100644
index 0000000..df11206
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/EnableWidgetDialog.kt
@@ -0,0 +1,143 @@
+/*
+ * 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.ui.compose
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import com.android.compose.theme.LocalAndroidColorScheme
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.ComponentSystemUIDialog
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
+import com.android.systemui.statusbar.phone.create
+
+/** Dialog shown upon tapping a disabled widget which allows users to enable the widget. */
+@Composable
+fun EnableWidgetDialog(
+ isEnableWidgetDialogVisible: Boolean,
+ dialogFactory: SystemUIDialogFactory,
+ title: String,
+ positiveButtonText: String,
+ onConfirm: () -> Unit,
+ onCancel: () -> Unit
+) {
+ var dialog: ComponentSystemUIDialog? by remember { mutableStateOf(null) }
+ val context = LocalView.current.context
+
+ DisposableEffect(isEnableWidgetDialogVisible) {
+ if (isEnableWidgetDialogVisible) {
+ dialog =
+ dialogFactory.create(
+ context = context,
+ ) {
+ DialogComposable(title, positiveButtonText, onConfirm, onCancel)
+ }
+ dialog?.apply {
+ setCancelable(true)
+ setCanceledOnTouchOutside(true)
+ setOnCancelListener { onCancel() }
+ show()
+ }
+ }
+
+ onDispose {
+ dialog?.dismiss()
+ dialog = null
+ }
+ }
+}
+
+@Composable
+private fun DialogComposable(
+ title: String,
+ positiveButtonText: String,
+ onConfirm: () -> Unit,
+ onCancel: () -> Unit,
+) {
+ Box(
+ Modifier.fillMaxWidth()
+ .padding(top = 18.dp, bottom = 8.dp)
+ .background(LocalAndroidColorScheme.current.surfaceBright, RoundedCornerShape(28.dp))
+ ) {
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ verticalArrangement = Arrangement.spacedBy(20.dp),
+ ) {
+ Box(
+ modifier = Modifier.padding(horizontal = 24.dp).fillMaxWidth().wrapContentHeight(),
+ contentAlignment = Alignment.TopStart
+ ) {
+ Text(
+ text = title,
+ style = MaterialTheme.typography.titleMedium,
+ color = LocalAndroidColorScheme.current.onSurface,
+ textAlign = TextAlign.Center,
+ maxLines = 1,
+ )
+ }
+
+ Box(
+ modifier = Modifier.padding(end = 12.dp).fillMaxWidth().wrapContentHeight(),
+ contentAlignment = Alignment.Center
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.End,
+ ) {
+ TextButton(
+ contentPadding = PaddingValues(16.dp),
+ onClick = onCancel,
+ ) {
+ Text(
+ text = stringResource(R.string.cancel),
+ )
+ }
+ TextButton(
+ contentPadding = PaddingValues(16.dp),
+ onClick = onConfirm,
+ ) {
+ Text(
+ text = positiveButtonText,
+ )
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
index bc1e429..379c165 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
@@ -30,16 +30,18 @@
import kotlinx.coroutines.coroutineScope
/**
- * Observe taps without actually consuming them, so child elements can still respond to them. Long
+ * Observe taps without consuming them by default, so child elements can still respond to them. Long
* presses are excluded.
*/
-suspend fun PointerInputScope.observeTapsWithoutConsuming(
+suspend fun PointerInputScope.observeTaps(
pass: PointerEventPass = PointerEventPass.Initial,
+ shouldConsume: Boolean = false,
onTap: ((Offset) -> Unit)? = null,
) = coroutineScope {
if (onTap == null) return@coroutineScope
awaitEachGesture {
- awaitFirstDown(pass = pass)
+ val down = awaitFirstDown(pass = pass)
+ if (shouldConsume) down.consume()
val tapTimeout = viewConfiguration.longPressTimeoutMillis
val up = withTimeoutOrNull(tapTimeout) { waitForUpOrCancellation(pass = pass) }
if (up != null) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
index b5499b7..bc4e555 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
@@ -18,13 +18,16 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalView
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayout
import com.android.compose.animation.scene.transitions
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import javax.inject.Inject
@@ -40,6 +43,7 @@
constructor(
private val viewModel: LockscreenContentViewModel,
private val blueprints: Set<@JvmSuppressWildcards ComposableLockscreenSceneBlueprint>,
+ private val clockInteractor: KeyguardClockInteractor,
) {
private val sceneKeyByBlueprint: Map<ComposableLockscreenSceneBlueprint, SceneKey> by lazy {
@@ -55,6 +59,12 @@
) {
val coroutineScope = rememberCoroutineScope()
val blueprintId by viewModel.blueprintId(coroutineScope).collectAsState()
+ val view = LocalView.current
+ DisposableEffect(view) {
+ clockInteractor.clockEventController.registerListeners(view)
+
+ onDispose { clockInteractor.clockEventController.unregisterListeners() }
+ }
// Switch smoothly between blueprints, any composable tagged with element() will be
// transition-animated between any two blueprints, if they both display the same element.
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 96520b2..7acb4d5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -19,61 +19,31 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import com.android.compose.animation.scene.Edge
-import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneScope
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
import dagger.Lazy
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.stateIn
/** The lock screen scene shows when the device is locked. */
@SysUISingleton
class LockscreenScene
@Inject
constructor(
- @Application private val applicationScope: CoroutineScope,
viewModel: LockscreenSceneViewModel,
private val lockscreenContent: Lazy<LockscreenContent>,
) : ComposableScene {
override val key = Scenes.Lockscreen
override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
- combine(
- viewModel.upDestinationSceneKey,
- viewModel.leftDestinationSceneKey,
- viewModel.downFromTopEdgeDestinationSceneKey,
- ) { upKey, leftKey, downFromTopEdgeKey ->
- destinationScenes(
- up = upKey,
- left = leftKey,
- downFromTopEdge = downFromTopEdgeKey,
- )
- }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue =
- destinationScenes(
- up = viewModel.upDestinationSceneKey.value,
- left = viewModel.leftDestinationSceneKey.value,
- downFromTopEdge = viewModel.downFromTopEdgeDestinationSceneKey.value,
- )
- )
+ viewModel.destinationScenes
@Composable
override fun SceneScope.Content(
@@ -84,22 +54,6 @@
modifier = modifier,
)
}
-
- private fun destinationScenes(
- up: SceneKey?,
- left: SceneKey?,
- downFromTopEdge: SceneKey?,
- ): Map<UserAction, UserActionResult> {
- return buildMap {
- up?.let { this[Swipe(SwipeDirection.Up)] = UserActionResult(up) }
- left?.let { this[Swipe(SwipeDirection.Left)] = UserActionResult(left) }
- downFromTopEdge?.let {
- this[Swipe(fromSource = Edge.Top, direction = SwipeDirection.Down)] =
- UserActionResult(downFromTopEdge)
- }
- this[Swipe(direction = SwipeDirection.Down)] = UserActionResult(Scenes.Shade)
- }
- }
}
@Composable
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt
new file mode 100644
index 0000000..c5ff859
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.composable.blueprint
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.compose.animation.scene.transitions
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.largeClockElementKey
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smallClockElementKey
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smartspaceElementKey
+import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.ClockFaceInTransition.Companion.CLOCK_IN_MILLIS
+import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.ClockFaceInTransition.Companion.CLOCK_IN_START_DELAY_MILLIS
+import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.ClockFaceOutTransition.Companion.CLOCK_OUT_MILLIS
+import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.SmartspaceMoveTransition.Companion.STATUS_AREA_MOVE_DOWN_MILLIS
+import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.SmartspaceMoveTransition.Companion.STATUS_AREA_MOVE_UP_MILLIS
+
+object ClockTransition {
+ val defaultClockTransitions = transitions {
+ from(ClockScenes.smallClockScene, to = ClockScenes.largeClockScene) {
+ transitioningToLargeClock()
+ }
+ from(ClockScenes.largeClockScene, to = ClockScenes.smallClockScene) {
+ transitioningToSmallClock()
+ }
+ }
+
+ private fun TransitionBuilder.transitioningToLargeClock() {
+ spec = tween(durationMillis = STATUS_AREA_MOVE_UP_MILLIS.toInt())
+ timestampRange(
+ startMillis = CLOCK_IN_START_DELAY_MILLIS.toInt(),
+ endMillis = (CLOCK_IN_START_DELAY_MILLIS + CLOCK_IN_MILLIS).toInt()
+ ) {
+ fade(largeClockElementKey)
+ }
+
+ timestampRange(endMillis = CLOCK_OUT_MILLIS.toInt()) { fade(smallClockElementKey) }
+ anchoredTranslate(smallClockElementKey, smartspaceElementKey)
+ }
+
+ private fun TransitionBuilder.transitioningToSmallClock() {
+ spec = tween(durationMillis = STATUS_AREA_MOVE_DOWN_MILLIS.toInt())
+ timestampRange(
+ startMillis = CLOCK_IN_START_DELAY_MILLIS.toInt(),
+ endMillis = (CLOCK_IN_START_DELAY_MILLIS + CLOCK_IN_MILLIS).toInt()
+ ) {
+ fade(smallClockElementKey)
+ }
+
+ timestampRange(endMillis = CLOCK_OUT_MILLIS.toInt()) { fade(largeClockElementKey) }
+ anchoredTranslate(smallClockElementKey, smartspaceElementKey)
+ }
+}
+
+object ClockScenes {
+ val smallClockScene = SceneKey("small-clock-scene")
+ val largeClockScene = SceneKey("large-clock-scene")
+}
+
+object ClockElementKeys {
+ val largeClockElementKey = ElementKey("large-clock")
+ val smallClockElementKey = ElementKey("small-clock")
+ val smartspaceElementKey = ElementKey("smart-space")
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index d23cd0c..9509fd2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -17,19 +17,14 @@
package com.android.systemui.keyguard.ui.composable.blueprint
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
-import com.android.compose.modifiers.padding
-import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -38,11 +33,8 @@
import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection
import com.android.systemui.keyguard.ui.composable.section.NotificationSection
import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
-import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
-import com.android.systemui.media.controls.ui.composable.MediaCarousel
-import com.android.systemui.res.R
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
@@ -59,14 +51,12 @@
private val viewModel: LockscreenContentViewModel,
private val statusBarSection: StatusBarSection,
private val clockSection: DefaultClockSection,
- private val smartSpaceSection: SmartSpaceSection,
private val notificationSection: NotificationSection,
private val lockSection: LockSection,
private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
private val bottomAreaSection: BottomAreaSection,
private val settingsMenuSection: SettingsMenuSection,
private val mediaCarouselSection: MediaCarouselSection,
- private val clockInteractor: KeyguardClockInteractor,
) : ComposableLockscreenSceneBlueprint {
override val id: String = "default"
@@ -74,7 +64,6 @@
@Composable
override fun SceneScope.Content(modifier: Modifier) {
val isUdfpsVisible = viewModel.isUdfpsVisible
- val burnIn = rememberBurnIn(clockInteractor)
val resources = LocalContext.current.resources
LockscreenLongPress(
@@ -88,40 +77,7 @@
modifier = Modifier.fillMaxWidth(),
) {
with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
- with(clockSection) {
- SmallClock(
- burnInParams = burnIn.parameters,
- onTopChanged = burnIn.onSmallClockTopChanged,
- modifier = Modifier.fillMaxWidth(),
- )
- }
- with(smartSpaceSection) {
- SmartSpace(
- burnInParams = burnIn.parameters,
- onTopChanged = burnIn.onSmartspaceTopChanged,
- modifier =
- Modifier.fillMaxWidth()
- .padding(
- top = { viewModel.getSmartSpacePaddingTop(resources) },
- )
- .padding(
- bottom =
- dimensionResource(
- R.dimen.keyguard_status_view_bottom_margin
- ),
- ),
- )
- }
-
- if (viewModel.isLargeClockVisible) {
- Spacer(modifier = Modifier.weight(weight = 1f))
- with(clockSection) {
- LargeClock(
- modifier = Modifier.fillMaxWidth(),
- )
- }
- }
-
+ with(clockSection) { DefaultClockLayout() }
with(mediaCarouselSection) { MediaCarousel() }
if (viewModel.areNotificationsVisible(resources = resources)) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index c422c4b..9abfa42 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -17,19 +17,14 @@
package com.android.systemui.keyguard.ui.composable.blueprint
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
-import com.android.compose.modifiers.padding
-import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -38,10 +33,8 @@
import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection
import com.android.systemui.keyguard.ui.composable.section.NotificationSection
import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
-import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
-import com.android.systemui.res.R
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
@@ -58,14 +51,12 @@
private val viewModel: LockscreenContentViewModel,
private val statusBarSection: StatusBarSection,
private val clockSection: DefaultClockSection,
- private val smartSpaceSection: SmartSpaceSection,
private val notificationSection: NotificationSection,
private val lockSection: LockSection,
private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
private val bottomAreaSection: BottomAreaSection,
private val settingsMenuSection: SettingsMenuSection,
private val mediaCarouselSection: MediaCarouselSection,
- private val clockInteractor: KeyguardClockInteractor,
) : ComposableLockscreenSceneBlueprint {
override val id: String = "shortcuts-besides-udfps"
@@ -73,7 +64,6 @@
@Composable
override fun SceneScope.Content(modifier: Modifier) {
val isUdfpsVisible = viewModel.isUdfpsVisible
- val burnIn = rememberBurnIn(clockInteractor)
val resources = LocalContext.current.resources
LockscreenLongPress(
@@ -87,36 +77,7 @@
modifier = Modifier.fillMaxWidth(),
) {
with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
- with(clockSection) {
- SmallClock(
- onTopChanged = burnIn.onSmallClockTopChanged,
- modifier = Modifier.fillMaxWidth(),
- burnInParams = burnIn.parameters,
- )
- }
- with(smartSpaceSection) {
- SmartSpace(
- burnInParams = burnIn.parameters,
- onTopChanged = burnIn.onSmartspaceTopChanged,
- modifier =
- Modifier.fillMaxWidth()
- .padding(
- top = { viewModel.getSmartSpacePaddingTop(resources) }
- )
- .padding(
- bottom =
- dimensionResource(
- R.dimen.keyguard_status_view_bottom_margin
- )
- ),
- )
- }
-
- if (viewModel.isLargeClockVisible) {
- Spacer(modifier = Modifier.weight(weight = 1f))
- with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
- }
-
+ with(clockSection) { DefaultClockLayout() }
with(mediaCarouselSection) { MediaCarousel() }
if (viewModel.areNotificationsVisible(resources = resources)) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
index d218425..652412d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
@@ -18,7 +18,6 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -27,7 +26,6 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntRect
@@ -35,7 +33,6 @@
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.padding
import com.android.systemui.Flags
-import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -44,7 +41,6 @@
import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection
import com.android.systemui.keyguard.ui.composable.section.NotificationSection
import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
-import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import com.android.systemui.res.R
@@ -65,14 +61,12 @@
private val viewModel: LockscreenContentViewModel,
private val statusBarSection: StatusBarSection,
private val clockSection: DefaultClockSection,
- private val smartSpaceSection: SmartSpaceSection,
private val notificationSection: NotificationSection,
private val lockSection: LockSection,
private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
private val bottomAreaSection: BottomAreaSection,
private val settingsMenuSection: SettingsMenuSection,
private val mediaCarouselSection: MediaCarouselSection,
- private val clockInteractor: KeyguardClockInteractor,
private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
) : ComposableLockscreenSceneBlueprint {
@@ -81,8 +75,6 @@
@Composable
override fun SceneScope.Content(modifier: Modifier) {
val isUdfpsVisible = viewModel.isUdfpsVisible
- val burnIn = rememberBurnIn(clockInteractor)
- val resources = LocalContext.current.resources
LockscreenLongPress(
viewModel = viewModel.longPress,
@@ -102,41 +94,7 @@
modifier = Modifier.fillMaxHeight().weight(weight = 1f),
horizontalAlignment = Alignment.CenterHorizontally,
) {
- with(clockSection) {
- SmallClock(
- burnInParams = burnIn.parameters,
- onTopChanged = burnIn.onSmallClockTopChanged,
- modifier = Modifier.fillMaxWidth(),
- )
- }
-
- with(smartSpaceSection) {
- SmartSpace(
- burnInParams = burnIn.parameters,
- onTopChanged = burnIn.onSmartspaceTopChanged,
- modifier =
- Modifier.fillMaxWidth()
- .padding(
- top = {
- viewModel.getSmartSpacePaddingTop(resources)
- },
- )
- .padding(
- bottom =
- dimensionResource(
- R.dimen
- .keyguard_status_view_bottom_margin
- )
- ),
- )
- }
-
- if (viewModel.isLargeClockVisible) {
- Spacer(modifier = Modifier.weight(weight = 1f))
- with(clockSection) { LargeClock() }
- Spacer(modifier = Modifier.weight(weight = 1f))
- }
-
+ with(clockSection) { DefaultClockLayout() }
with(mediaCarouselSection) { MediaCarousel() }
}
with(notificationSection) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
index 152cc67..1ab2bc76 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
@@ -18,27 +18,37 @@
import android.view.ViewGroup
import android.widget.FrameLayout
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.viewinterop.AndroidView
-import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.SceneTransitionLayout
import com.android.compose.modifiers.padding
-import com.android.keyguard.KeyguardClockSwitch
import com.android.systemui.customization.R as customizationR
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.largeClockElementKey
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smallClockElementKey
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smartspaceElementKey
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.smallClockScene
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockTransition.defaultClockTransitions
+import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn
import com.android.systemui.keyguard.ui.composable.modifier.burnInAware
import com.android.systemui.keyguard.ui.composable.modifier.onTopPlacementChanged
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
+import com.android.systemui.res.R
import javax.inject.Inject
/** Provides small clock and large clock composables for the default clock face. */
@@ -48,112 +58,152 @@
private val viewModel: KeyguardClockViewModel,
private val clockInteractor: KeyguardClockInteractor,
private val aodBurnInViewModel: AodBurnInViewModel,
+ private val lockscreenContentViewModel: LockscreenContentViewModel,
+ private val smartSpaceSection: SmartSpaceSection,
) {
+ @Composable
+ fun DefaultClockLayout(
+ modifier: Modifier = Modifier,
+ ) {
+ val isLargeClockVisible by viewModel.isLargeClockVisible.collectAsState()
+ val burnIn = rememberBurnIn(clockInteractor)
+ val currentScene =
+ if (isLargeClockVisible) {
+ largeClockScene
+ } else {
+ smallClockScene
+ }
+
+ LaunchedEffect(isLargeClockVisible) {
+ if (isLargeClockVisible) {
+ burnIn.onSmallClockTopChanged(null)
+ }
+ }
+
+ SceneTransitionLayout(
+ modifier = modifier,
+ currentScene = currentScene,
+ onChangeScene = {},
+ transitions = defaultClockTransitions,
+ ) {
+ scene(smallClockScene) {
+ Column {
+ SmallClock(
+ burnInParams = burnIn.parameters,
+ onTopChanged = burnIn.onSmallClockTopChanged,
+ modifier = Modifier.element(smallClockElementKey).fillMaxWidth()
+ )
+ SmartSpaceContent()
+ }
+ }
+
+ scene(largeClockScene) {
+ Column {
+ SmartSpaceContent()
+ LargeClock(modifier = Modifier.element(largeClockElementKey).fillMaxWidth())
+ }
+ }
+ }
+ }
@Composable
- fun SceneScope.SmallClock(
+ private fun SceneScope.SmartSpaceContent(
+ modifier: Modifier = Modifier,
+ ) {
+ val burnIn = rememberBurnIn(clockInteractor)
+ val resources = LocalContext.current.resources
+
+ MovableElement(key = smartspaceElementKey, modifier = modifier) {
+ content {
+ with(smartSpaceSection) {
+ this@SmartSpaceContent.SmartSpace(
+ burnInParams = burnIn.parameters,
+ onTopChanged = burnIn.onSmartspaceTopChanged,
+ modifier =
+ Modifier.fillMaxWidth()
+ .padding(
+ top = {
+ lockscreenContentViewModel.getSmartSpacePaddingTop(
+ resources
+ )
+ },
+ bottom = {
+ resources.getDimensionPixelSize(
+ R.dimen.keyguard_status_view_bottom_margin
+ )
+ }
+ ),
+ )
+ }
+ }
+ }
+ }
+
+ @Composable
+ private fun SceneScope.SmallClock(
burnInParams: BurnInParameters,
onTopChanged: (top: Float?) -> Unit,
modifier: Modifier = Modifier,
) {
- val clockSize by viewModel.clockSize.collectAsState()
val currentClock by viewModel.currentClock.collectAsState()
- viewModel.clock = currentClock
-
- if (clockSize != KeyguardClockSwitch.SMALL || currentClock?.smallClock?.view == null) {
- onTopChanged(null)
+ if (currentClock?.smallClock?.view == null) {
return
}
+ viewModel.clock = currentClock
- val view = LocalView.current
+ val context = LocalContext.current
- DisposableEffect(view) {
- clockInteractor.clockEventController.registerListeners(view)
-
- onDispose { clockInteractor.clockEventController.unregisterListeners() }
- }
-
- MovableElement(
- key = ClockElementKey,
- modifier = modifier,
- ) {
- content {
- AndroidView(
- factory = { context ->
- FrameLayout(context).apply {
- val newClockView = checkNotNull(currentClock).smallClock.view
- (newClockView.parent as? ViewGroup)?.removeView(newClockView)
- addView(newClockView)
- }
- },
- modifier =
- Modifier.padding(
- horizontal =
- dimensionResource(customizationR.dimen.clock_padding_start)
- )
- .padding(top = { viewModel.getSmallClockTopMargin(view.context) })
- .onTopPlacementChanged(onTopChanged)
- .burnInAware(
- viewModel = aodBurnInViewModel,
- params = burnInParams,
- ),
- update = {
- val newClockView = checkNotNull(currentClock).smallClock.view
- it.removeAllViews()
- (newClockView.parent as? ViewGroup)?.removeView(newClockView)
- it.addView(newClockView)
- },
- )
- }
- }
+ AndroidView(
+ factory = { context ->
+ FrameLayout(context).apply {
+ val newClockView = checkNotNull(currentClock).smallClock.view
+ (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+ addView(newClockView)
+ }
+ },
+ update = {
+ val newClockView = checkNotNull(currentClock).smallClock.view
+ it.removeAllViews()
+ (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+ it.addView(newClockView)
+ },
+ modifier =
+ modifier
+ .padding(
+ horizontal = dimensionResource(customizationR.dimen.clock_padding_start)
+ )
+ .padding(top = { viewModel.getSmallClockTopMargin(context) })
+ .onTopPlacementChanged(onTopChanged)
+ .burnInAware(
+ viewModel = aodBurnInViewModel,
+ params = burnInParams,
+ ),
+ )
}
@Composable
- fun SceneScope.LargeClock(modifier: Modifier = Modifier) {
- val clockSize by viewModel.clockSize.collectAsState()
+ private fun SceneScope.LargeClock(modifier: Modifier = Modifier) {
val currentClock by viewModel.currentClock.collectAsState()
viewModel.clock = currentClock
-
- if (clockSize != KeyguardClockSwitch.LARGE) {
- return
- }
-
if (currentClock?.largeClock?.view == null) {
return
}
- val view = LocalView.current
-
- DisposableEffect(view) {
- clockInteractor.clockEventController.registerListeners(view)
-
- onDispose { clockInteractor.clockEventController.unregisterListeners() }
- }
-
- MovableElement(
- key = ClockElementKey,
- modifier = modifier,
- ) {
- content {
- AndroidView(
- factory = { context ->
- FrameLayout(context).apply {
- val newClockView = checkNotNull(currentClock).largeClock.view
- (newClockView.parent as? ViewGroup)?.removeView(newClockView)
- addView(newClockView)
- }
- },
- update = {
- val newClockView = checkNotNull(currentClock).largeClock.view
- it.removeAllViews()
- (newClockView.parent as? ViewGroup)?.removeView(newClockView)
- it.addView(newClockView)
- },
- modifier = Modifier.fillMaxSize()
- )
- }
- }
+ AndroidView(
+ factory = { context ->
+ FrameLayout(context).apply {
+ val newClockView = checkNotNull(currentClock).largeClock.view
+ (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+ addView(newClockView)
+ }
+ },
+ update = {
+ val newClockView = checkNotNull(currentClock).largeClock.view
+ it.removeAllViews()
+ (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+ it.addView(newClockView)
+ },
+ modifier = modifier.fillMaxSize()
+ )
}
}
-
-private val ClockElementKey = ElementKey("Clock")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
index 9b71844..d1cc53e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
@@ -33,7 +33,6 @@
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
-import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
import com.android.systemui.keyguard.ui.composable.modifier.burnInAware
@@ -60,7 +59,7 @@
modifier: Modifier = Modifier,
) {
Column(
- modifier = modifier.element(SmartSpaceElementKey).onTopPlacementChanged(onTopChanged),
+ modifier = modifier.onTopPlacementChanged(onTopChanged),
) {
if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) {
return
@@ -192,5 +191,3 @@
)
}
}
-
-private val SmartSpaceElementKey = ElementKey("SmartSpace")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 0b9f503..31201c2f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -73,6 +73,7 @@
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager
@@ -152,27 +153,29 @@
mediaHost: MediaHost,
modifier: Modifier = Modifier,
) {
- val isSplitShade by viewModel.isSplitShade.collectAsState()
- if (isSplitShade) {
- SplitShade(
- viewModel = viewModel,
- createTintedIconManager = createTintedIconManager,
- createBatteryMeterViewController = createBatteryMeterViewController,
- statusBarIconController = statusBarIconController,
- mediaCarouselController = mediaCarouselController,
- mediaHost = mediaHost,
- modifier = modifier,
- )
- } else {
- SingleShade(
- viewModel = viewModel,
- createTintedIconManager = createTintedIconManager,
- createBatteryMeterViewController = createBatteryMeterViewController,
- statusBarIconController = statusBarIconController,
- mediaCarouselController = mediaCarouselController,
- mediaHost = mediaHost,
- modifier = modifier,
- )
+ val shadeMode by viewModel.shadeMode.collectAsState()
+ when (shadeMode) {
+ is ShadeMode.Single ->
+ SingleShade(
+ viewModel = viewModel,
+ createTintedIconManager = createTintedIconManager,
+ createBatteryMeterViewController = createBatteryMeterViewController,
+ statusBarIconController = statusBarIconController,
+ mediaCarouselController = mediaCarouselController,
+ mediaHost = mediaHost,
+ modifier = modifier,
+ )
+ is ShadeMode.Split ->
+ SplitShade(
+ viewModel = viewModel,
+ createTintedIconManager = createTintedIconManager,
+ createBatteryMeterViewController = createBatteryMeterViewController,
+ statusBarIconController = statusBarIconController,
+ mediaCarouselController = mediaCarouselController,
+ mediaHost = mediaHost,
+ modifier = modifier,
+ )
+ is ShadeMode.Dual -> error("Dual shade is not yet implemented!")
}
}
@@ -370,7 +373,6 @@
) {
if (viewModel.isMediaVisible()) {
val density = LocalDensity.current
- val layoutWidth = remember { mutableStateOf(0) }
val mediaHeight = dimensionResource(R.dimen.qs_media_session_height_expanded)
MediaCarousel(
@@ -386,7 +388,7 @@
layout(placeable.width, placeable.height) { placeable.placeRelative(0, 0) }
},
mediaHost = mediaHost,
- layoutWidth = layoutWidth.value,
+ layoutWidth = 0,
layoutHeight = with(density) { mediaHeight.toPx() }.toInt(),
carouselController = mediaCarouselController,
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
index 8ac84ff..b1fbe35 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.panel.component.anc.ui.composable
import android.content.Context
+import android.view.ContextThemeWrapper
import android.view.View
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.MaterialTheme
@@ -73,15 +74,16 @@
AndroidView<SliceView>(
modifier = Modifier.fillMaxWidth(),
factory = { context: Context ->
- SliceView(context).apply {
- mode = SliceView.MODE_LARGE
- isScrollable = false
- importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
- setShowTitleItems(true)
- addOnLayoutChangeListener(
- OnWidthChangedLayoutListener(viewModel::changeSliceWidth)
- )
- }
+ SliceView(ContextThemeWrapper(context, R.style.Widget_SliceView_VolumePanel))
+ .apply {
+ mode = SliceView.MODE_LARGE
+ isScrollable = false
+ importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+ setShowTitleItems(true)
+ addOnLayoutChangeListener(
+ OnWidthChangedLayoutListener(viewModel::changeSliceWidth)
+ )
+ }
},
update = { sliceView: SliceView -> sliceView.slice = slice }
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
index 4d810df..81d2da0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
@@ -42,9 +42,6 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
@@ -61,12 +58,13 @@
@Composable
fun ColumnVolumeSliders(
viewModels: List<SliderViewModel>,
+ isExpanded: Boolean,
+ onExpandedChanged: (Boolean) -> Unit,
sliderColors: PlatformSliderColors,
isExpandable: Boolean,
modifier: Modifier = Modifier,
) {
require(viewModels.isNotEmpty())
- var isExpanded: Boolean by remember(isExpandable) { mutableStateOf(!isExpandable) }
val transition = updateTransition(isExpanded, label = "CollapsableSliders")
Column(modifier = modifier) {
Row(
@@ -85,7 +83,7 @@
if (isExpandable) {
ExpandButton(
isExpanded = isExpanded,
- onExpandedChanged = { isExpanded = it },
+ onExpandedChanged = onExpandedChanged,
sliderColors,
Modifier,
)
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 18a62dc..3e0aee5 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
@@ -22,6 +22,7 @@
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -30,6 +31,7 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
import com.android.compose.PlatformSlider
import com.android.compose.PlatformSliderColors
import com.android.systemui.common.ui.compose.Icon
@@ -54,7 +56,7 @@
if (isDragging) {
Text(text = value.toInt().toString())
} else {
- state.icon?.let { Icon(icon = it) }
+ state.icon?.let { Icon(modifier = Modifier.size(24.dp), icon = it) }
}
},
colors = sliderColors,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt
index 1ca18de..fdf8ee8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt
@@ -48,8 +48,11 @@
modifier = modifier.fillMaxWidth(),
)
} else {
+ val isExpanded by viewModel.isExpanded.collectAsState()
ColumnVolumeSliders(
viewModels = sliderViewModels,
+ isExpanded = isExpanded,
+ onExpandedChanged = viewModel::onExpandedChanged,
sliderColors = PlatformSliderDefaults.defaultPlatformSliderColors(),
isExpandable = isPortrait,
modifier = modifier.fillMaxWidth(),
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
index b9b472f..6cff30c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
@@ -21,6 +21,7 @@
import androidx.compose.animation.core.SpringSpec
import kotlin.math.absoluteValue
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
/**
@@ -147,13 +148,16 @@
}
// Animate the progress to its target value.
- launch { animatable.animateTo(targetProgress, animationSpec) }
- .invokeOnCompletion {
- // Settle the state to Idle(target). Note that this will do nothing if this transition
- // was replaced/interrupted by another one, and this also runs if this coroutine is
- // cancelled, i.e. if [this] coroutine scope is cancelled.
- layoutState.finishTransition(transition, target)
- }
+ transition.job =
+ launch { animatable.animateTo(targetProgress, animationSpec) }
+ .apply {
+ invokeOnCompletion {
+ // Settle the state to Idle(target). Note that this will do nothing if this
+ // transition was replaced/interrupted by another one, and this also runs if
+ // this coroutine is cancelled, i.e. if [this] coroutine scope is cancelled.
+ layoutState.finishTransition(transition, target)
+ }
+ }
return transition
}
@@ -174,8 +178,13 @@
*/
lateinit var animatable: Animatable<Float, AnimationVector1D>
+ /** The job that is animating [animatable]. */
+ lateinit var job: Job
+
override val progress: Float
get() = animatable.value
+
+ override fun finish(): Job = job
}
// TODO(b/290184746): Compute a good default visibility threshold that depends on the layout size
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index b94e49b..63ec54f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -20,6 +20,7 @@
import android.util.Log
import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.SpringSpec
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.runtime.getValue
@@ -30,6 +31,7 @@
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.round
+import com.android.compose.animation.scene.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
import kotlin.math.absoluteValue
import kotlinx.coroutines.CoroutineScope
@@ -96,9 +98,15 @@
return false
}
+ val swipeTransition = dragController.swipeTransition
+
+ // Don't intercept a transition that is finishing.
+ if (swipeTransition.isFinishing) {
+ return false
+ }
+
// Only intercept the current transition if one of the 2 swipes results is also a transition
// between the same pair of scenes.
- val swipeTransition = dragController.swipeTransition
val fromScene = swipeTransition._currentScene
val swipes = computeSwipes(fromScene, startedPosition, pointersDown = 1)
val (upOrLeft, downOrRight) = swipes.computeSwipesResults(fromScene)
@@ -149,15 +157,24 @@
val fromScene = layoutImpl.scene(transitionState.currentScene)
val swipes = computeSwipes(fromScene, startedPosition, pointersDown)
- val result = swipes.findUserActionResult(fromScene, overSlop, true)
-
- // As we were unable to locate a valid target scene, the initial SwipeTransition cannot be
- // defined. Consequently, a simple NoOp Controller will be returned.
- if (result == null) return NoOpDragController
+ val result =
+ swipes.findUserActionResult(fromScene, overSlop, true)
+ // As we were unable to locate a valid target scene, the initial SwipeTransition
+ // cannot be defined. Consequently, a simple NoOp Controller will be returned.
+ ?: return NoOpDragController
return updateDragController(
swipes = swipes,
- swipeTransition = SwipeTransition(fromScene, result, swipes, layoutImpl, orientation)
+ swipeTransition =
+ SwipeTransition(
+ layoutImpl.state,
+ coroutineScope,
+ fromScene,
+ result,
+ swipes,
+ layoutImpl,
+ orientation,
+ )
)
}
@@ -277,7 +294,7 @@
* @return the consumed delta
*/
override fun onDrag(delta: Float) {
- if (delta == 0f || !isDrivingTransition) return
+ if (delta == 0f || !isDrivingTransition || swipeTransition.isFinishing) return
swipeTransition.dragOffset += delta
val (fromScene, acceleratedOffset) =
@@ -305,6 +322,8 @@
) {
val swipeTransition =
SwipeTransition(
+ layoutState = layoutState,
+ coroutineScope = draggableHandler.coroutineScope,
fromScene = fromScene,
result = result,
swipes = swipes,
@@ -335,10 +354,7 @@
// If the swipe was not committed or if the swipe distance is not computed yet, don't do
// anything.
- if (
- swipeTransition._currentScene != toScene ||
- distance == SwipeTransition.DistanceUnspecified
- ) {
+ if (swipeTransition._currentScene != toScene || distance == DistanceUnspecified) {
return fromScene to 0f
}
@@ -355,15 +371,9 @@
}
}
- private fun snapToScene(scene: SceneKey) {
- if (!isDrivingTransition) return
- swipeTransition.cancelOffsetAnimation()
- layoutState.finishTransition(swipeTransition, idleScene = scene)
- }
-
override fun onStop(velocity: Float, canChangeScene: Boolean) {
// The state was changed since the drag started; don't do anything.
- if (!isDrivingTransition) {
+ if (!isDrivingTransition || swipeTransition.isFinishing) {
return
}
@@ -389,7 +399,7 @@
coroutineScope = draggableHandler.coroutineScope,
initialVelocity = velocity,
targetOffset = targetOffset,
- onAnimationCompleted = { snapToScene(targetScene.key) }
+ targetScene = targetScene.key,
)
}
@@ -406,7 +416,7 @@
var targetScene: Scene
var targetOffset: Float
if (
- distance != SwipeTransition.DistanceUnspecified &&
+ distance != DistanceUnspecified &&
shouldCommitSwipe(
offset,
distance,
@@ -432,8 +442,8 @@
if (targetScene == fromScene) {
0f
} else {
- check(distance != SwipeTransition.DistanceUnspecified) {
- "distance is equal to ${SwipeTransition.DistanceUnspecified}"
+ check(distance != DistanceUnspecified) {
+ "distance is equal to $DistanceUnspecified"
}
distance
}
@@ -451,12 +461,14 @@
val result = swipes.findUserActionResultStrict(velocity)
if (result == null) {
// We will not animate
- snapToScene(fromScene.key)
+ swipeTransition.snapToScene(fromScene.key)
return
}
val newSwipeTransition =
SwipeTransition(
+ layoutState = layoutState,
+ coroutineScope = draggableHandler.coroutineScope,
fromScene = fromScene,
result = result,
swipes = swipes,
@@ -514,6 +526,8 @@
}
private fun SwipeTransition(
+ layoutState: BaseSceneTransitionLayoutState,
+ coroutineScope: CoroutineScope,
fromScene: Scene,
result: UserActionResult,
swipes: Swipes,
@@ -530,6 +544,8 @@
}
return SwipeTransition(
+ layoutState = layoutState,
+ coroutineScope = coroutineScope,
key = result.transitionKey,
_fromScene = fromScene,
_toScene = layoutImpl.scene(result.toScene),
@@ -541,6 +557,8 @@
private fun SwipeTransition(old: SwipeTransition): SwipeTransition {
return SwipeTransition(
+ layoutState = old.layoutState,
+ coroutineScope = old.coroutineScope,
key = old.key,
_fromScene = old._fromScene,
_toScene = old._toScene,
@@ -555,6 +573,8 @@
}
private class SwipeTransition(
+ val layoutState: BaseSceneTransitionLayoutState,
+ val coroutineScope: CoroutineScope,
val key: TransitionKey?,
val _fromScene: Scene,
val _toScene: Scene,
@@ -573,7 +593,7 @@
// Important: If we are going to return early because distance is equal to 0, we should
// still make sure we read the offset before returning so that the calling code still
// subscribes to the offset value.
- val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset
+ val offset = offsetAnimation?.animatable?.value ?: dragOffset
val distance = distance()
if (distance == DistanceUnspecified) {
@@ -588,20 +608,11 @@
/** The current offset caused by the drag gesture. */
var dragOffset by mutableFloatStateOf(0f)
- /**
- * Whether the offset is animated (the user lifted their finger) or if it is driven by gesture.
- */
- var isAnimatingOffset by mutableStateOf(false)
+ /** The offset animation that animates the offset once the user lifts their finger. */
+ private var offsetAnimation: OffsetAnimation? by mutableStateOf(null)
- // If we are not animating offset, it means the offset is being driven by the user's finger.
override val isUserInputOngoing: Boolean
- get() = !isAnimatingOffset
-
- /** The animatable used to animate the offset once the user lifted its finger. */
- val offsetAnimatable = Animatable(0f, OffsetVisibilityThreshold)
-
- /** Job to check that there is at most one offset animation in progress. */
- private var offsetAnimationJob: Job? = null
+ get() = offsetAnimation == null
/**
* The [TransformationSpecImpl] associated to this transition.
@@ -615,8 +626,18 @@
/** The spec to use when animating this transition to either [fromScene] or [toScene]. */
lateinit var swipeSpec: SpringSpec<Float>
+ override val overscrollScope: OverscrollScope =
+ object : OverscrollScope {
+ override val absoluteDistance: Float
+ get() = distance().absoluteValue
+ }
+
private var lastDistance = DistanceUnspecified
+ /** Whether [TransitionState.Transition.finish] was called on this transition. */
+ var isFinishing = false
+ private set
+
/**
* The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is above
* or to the left of [toScene].
@@ -647,25 +668,21 @@
return distance
}
- /** Ends any previous [offsetAnimationJob] and runs the new [job]. */
- private fun startOffsetAnimation(job: () -> Job) {
+ /** Ends any previous [offsetAnimation] and runs the new [animation]. */
+ private fun startOffsetAnimation(animation: () -> OffsetAnimation): OffsetAnimation {
cancelOffsetAnimation()
- offsetAnimationJob = job()
+ return animation().also { offsetAnimation = it }
}
/** Cancel any ongoing offset animation. */
// TODO(b/317063114) This should be a suspended function to avoid multiple jobs running at
// the same time.
fun cancelOffsetAnimation() {
- offsetAnimationJob?.cancel()
- finishOffsetAnimation()
- }
+ val animation = offsetAnimation ?: return
+ offsetAnimation = null
- fun finishOffsetAnimation() {
- if (isAnimatingOffset) {
- isAnimatingOffset = false
- dragOffset = offsetAnimatable.value
- }
+ dragOffset = animation.animatable.value
+ animation.job.cancel()
}
fun animateOffset(
@@ -673,35 +690,73 @@
coroutineScope: CoroutineScope,
initialVelocity: Float,
targetOffset: Float,
- onAnimationCompleted: () -> Unit,
- ) {
- startOffsetAnimation {
- coroutineScope.launch {
- animateOffset(targetOffset, initialVelocity)
- onAnimationCompleted()
+ targetScene: SceneKey,
+ ): OffsetAnimation {
+ return startOffsetAnimation {
+ val animatable = Animatable(dragOffset, OffsetVisibilityThreshold)
+ val job =
+ coroutineScope
+ .launch {
+ animatable.animateTo(
+ targetValue = targetOffset,
+ animationSpec = swipeSpec,
+ initialVelocity = initialVelocity,
+ )
+ }
+ // Make sure that we settle to target scene at the end of the animation or if
+ // the animation is cancelled.
+ .apply { invokeOnCompletion { snapToScene(targetScene) } }
+
+ OffsetAnimation(animatable, job)
+ }
+ }
+
+ fun snapToScene(scene: SceneKey) {
+ if (layoutState.transitionState != this) return
+ cancelOffsetAnimation()
+ layoutState.finishTransition(this, idleScene = scene)
+ }
+
+ override fun finish(): Job {
+ if (isFinishing) return requireNotNull(offsetAnimation).job
+ isFinishing = true
+
+ // If we were already animating the offset, simply return the job.
+ offsetAnimation?.let {
+ return it.job
+ }
+
+ // Animate to the current scene.
+ val targetScene = currentScene
+ val targetOffset =
+ if (targetScene == fromScene) {
+ 0f
+ } else {
+ val distance = distance()
+ check(distance != DistanceUnspecified) {
+ "targetScene != fromScene but distance is unspecified"
+ }
+ distance
}
- }
+
+ val animation =
+ animateOffset(
+ coroutineScope = coroutineScope,
+ initialVelocity = 0f,
+ targetOffset = targetOffset,
+ targetScene = currentScene,
+ )
+ check(offsetAnimation == animation)
+ return animation.job
}
- private suspend fun animateOffset(targetOffset: Float, initialVelocity: Float) {
- if (!isAnimatingOffset) {
- offsetAnimatable.snapTo(dragOffset)
- }
- isAnimatingOffset = true
+ internal class OffsetAnimation(
+ /** The animatable used to animate the offset. */
+ val animatable: Animatable<Float, AnimationVector1D>,
- val animationSpec = transformationSpec
- offsetAnimatable.animateTo(
- targetValue = targetOffset,
- animationSpec = swipeSpec,
- initialVelocity = initialVelocity,
- )
-
- finishOffsetAnimation()
- }
-
- companion object {
- const val DistanceUnspecified = 0f
- }
+ /** The job in which [animatable] is animated. */
+ val job: Job,
+ )
}
private object DefaultSwipeDistance : UserActionDistance {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
index cdc4778..be066fd 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -133,18 +133,14 @@
if (shouldComposeMovableElement) {
val movableContent: MovableElementContent =
layoutImpl.movableContents[element]
- ?: movableContentOf {
- contentScope: MovableElementContentScope,
- content: @Composable MovableElementContentScope.() -> Unit ->
- contentScope.content()
- }
+ ?: movableContentOf { content: @Composable () -> Unit -> content() }
.also { layoutImpl.movableContents[element] = it }
// Important: Don't introduce any parent Box or other layout here, because contentScope
// delegates its BoxScope implementation to the Box where this content() function is
// called, so it's important that this movableContent is composed directly under that
// Box.
- movableContent(contentScope, content)
+ movableContent { contentScope.content() }
} else {
// If we are not composed, we still need to lay out an empty space with the same *target
// size* as its movable content, i.e. the same *size when idle*. During transitions,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 1670e9c..25b0895 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -38,15 +38,8 @@
import com.android.compose.ui.util.lerp
import kotlinx.coroutines.CoroutineScope
-/**
- * The type for the content of movable elements.
- *
- * TODO(b/317972419): Revert back to make this movable content have a single @Composable lambda
- * parameter.
- */
-internal typealias MovableElementContent =
- @Composable
- (MovableElementContentScope, @Composable MovableElementContentScope.() -> Unit) -> Unit
+/** The type for the content of movable elements. */
+internal typealias MovableElementContent = @Composable (@Composable () -> Unit) -> Unit
@Stable
internal class SceneTransitionLayoutImpl(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 0fa19bb..e6f5d58 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -31,6 +31,7 @@
import com.android.compose.animation.scene.transition.link.StateLink
import kotlin.math.absoluteValue
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.Channel
/**
@@ -188,13 +189,8 @@
val fromScene: SceneKey,
/** The scene this transition is going to. Can't be the same as fromScene */
- val toScene: SceneKey
+ val toScene: SceneKey,
) : TransitionState {
-
- init {
- check(fromScene != toScene)
- }
-
/**
* The progress of the transition. This is usually in the `[0; 1]` range, but it can also be
* less than `0` or greater than `1` when using transitions with a spring AnimationSpec or
@@ -208,6 +204,21 @@
/** Whether user input is currently driving the transition. */
abstract val isUserInputOngoing: Boolean
+ init {
+ check(fromScene != toScene)
+ }
+
+ /**
+ * Force this transition to finish and animate to [currentScene], so that this transition
+ * progress will settle to either 0% (if [currentScene] == [fromScene]) or 100% (if
+ * [currentScene] == [toScene]) in a finite amount of time.
+ *
+ * @return the [Job] that animates the progress to [currentScene]. It can be used to wait
+ * until the animation is complete or cancel it to snap to [currentScene]. Calling
+ * [finish] multiple times will return the same [Job].
+ */
+ abstract fun finish(): Job
+
/**
* Whether we are transitioning. If [from] or [to] is empty, we will also check that they
* match the scenes we are animating from and/or to.
@@ -225,19 +236,28 @@
interface HasOverscrollProperties {
/**
- * The position of the [TransitionState.Transition.toScene].
+ * The position of the [Transition.toScene].
*
* Used to understand the direction of the overscroll.
*/
val isUpOrLeft: Boolean
/**
- * The relative orientation between [TransitionState.Transition.fromScene] and
- * [TransitionState.Transition.toScene].
+ * The relative orientation between [Transition.fromScene] and [Transition.toScene].
*
* Used to understand the orientation of the overscroll.
*/
val orientation: Orientation
+
+ /**
+ * Scope which can be used in the Overscroll DSL to define a transformation based on the
+ * distance between [Transition.fromScene] and [Transition.toScene].
+ */
+ val overscrollScope: OverscrollScope
+
+ companion object {
+ const val DistanceUnspecified = 0f
+ }
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index 2dd41cd..b466143 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -30,6 +30,7 @@
import com.android.compose.animation.scene.transformation.DrawScale
import com.android.compose.animation.scene.transformation.EdgeTranslate
import com.android.compose.animation.scene.transformation.Fade
+import com.android.compose.animation.scene.transformation.OverscrollTranslate
import com.android.compose.animation.scene.transformation.PropertyTransformation
import com.android.compose.animation.scene.transformation.RangedPropertyTransformation
import com.android.compose.animation.scene.transformation.ScaleSize
@@ -124,7 +125,7 @@
overscrollSpecs.fastForEach { spec ->
if (spec.orientation == orientation && filter(spec)) {
if (match != null) {
- error("Found multiple transition specs for transition $scene")
+ error("Found multiple overscroll specs for overscroll $scene")
}
match = spec
}
@@ -297,6 +298,7 @@
) {
when (current) {
is Translate,
+ is OverscrollTranslate,
is EdgeTranslate,
is AnchoredTranslate -> {
throwIfNotNull(offset, element, name = "offset")
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index bc52a28..2c109a3 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -22,6 +22,7 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
/** Define the [transitions][SceneTransitions] to be used with a [SceneTransitionLayout]. */
fun transitions(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions {
@@ -88,8 +89,7 @@
): OverscrollSpec
}
-@TransitionDsl
-interface OverscrollBuilder : PropertyTransformationBuilder {
+interface BaseTransitionBuilder : PropertyTransformationBuilder {
/**
* The distance it takes for this transition to animate from 0% to 100% when it is driven by a
* [UserAction].
@@ -120,7 +120,7 @@
}
@TransitionDsl
-interface TransitionBuilder : OverscrollBuilder, PropertyTransformationBuilder {
+interface TransitionBuilder : BaseTransitionBuilder {
/**
* The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when
* the transition is triggered (i.e. it is not gesture-based).
@@ -176,6 +176,24 @@
fun reversed(builder: TransitionBuilder.() -> Unit)
}
+@TransitionDsl
+interface OverscrollBuilder : BaseTransitionBuilder {
+ /** Translate the element(s) matching [matcher] by ([x], [y]) pixels. */
+ fun translate(
+ matcher: ElementMatcher,
+ x: OverscrollScope.() -> Float = { 0f },
+ y: OverscrollScope.() -> Float = { 0f },
+ )
+}
+
+interface OverscrollScope {
+ /**
+ * Return the absolute distance between fromScene and toScene, if available, otherwise
+ * [DistanceUnspecified].
+ */
+ val absoluteDistance: Float
+}
+
/**
* An interface to decide where we should draw shared Elements or compose MovableElements.
*
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index 65e8ea5..1c9080f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -31,6 +31,7 @@
import com.android.compose.animation.scene.transformation.DrawScale
import com.android.compose.animation.scene.transformation.EdgeTranslate
import com.android.compose.animation.scene.transformation.Fade
+import com.android.compose.animation.scene.transformation.OverscrollTranslate
import com.android.compose.animation.scene.transformation.PropertyTransformation
import com.android.compose.animation.scene.transformation.RangedPropertyTransformation
import com.android.compose.animation.scene.transformation.ScaleSize
@@ -114,7 +115,7 @@
}
}
-internal open class OverscrollBuilderImpl : OverscrollBuilder {
+internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder {
val transformations = mutableListOf<Transformation>()
private var range: TransformationRange? = null
protected var reversed = false
@@ -130,7 +131,7 @@
range = null
}
- private fun transformation(transformation: PropertyTransformation<*>) {
+ protected fun transformation(transformation: PropertyTransformation<*>) {
val transformation =
if (range != null) {
RangedPropertyTransformation(transformation, range!!)
@@ -185,7 +186,7 @@
}
}
-internal class TransitionBuilderImpl : OverscrollBuilderImpl(), TransitionBuilder {
+internal class TransitionBuilderImpl : BaseTransitionBuilderImpl(), TransitionBuilder {
override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow)
override var swipeSpec: SpringSpec<Float>? = null
override var distance: UserActionDistance? = null
@@ -226,3 +227,13 @@
fractionRange(start, end, builder)
}
}
+
+internal open class OverscrollBuilderImpl : BaseTransitionBuilderImpl(), OverscrollBuilder {
+ override fun translate(
+ matcher: ElementMatcher,
+ x: OverscrollScope.() -> Float,
+ y: OverscrollScope.() -> Float
+ ) {
+ transformation(OverscrollTranslate(matcher, x, y))
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
index 04d5914..849c9d7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
@@ -21,11 +21,11 @@
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.ElementMatcher
+import com.android.compose.animation.scene.OverscrollScope
import com.android.compose.animation.scene.Scene
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
import com.android.compose.animation.scene.TransitionState
-/** Translate an element by a fixed amount of density-independent pixels. */
internal class Translate(
override val matcher: ElementMatcher,
private val x: Dp = 0.dp,
@@ -47,3 +47,28 @@
}
}
}
+
+internal class OverscrollTranslate(
+ override val matcher: ElementMatcher,
+ val x: OverscrollScope.() -> Float = { 0f },
+ val y: OverscrollScope.() -> Float = { 0f },
+) : PropertyTransformation<Offset> {
+ override fun transform(
+ layoutImpl: SceneTransitionLayoutImpl,
+ scene: Scene,
+ element: Element,
+ sceneState: Element.SceneState,
+ transition: TransitionState.Transition,
+ value: Offset,
+ ): Offset {
+ // As this object is created by OverscrollBuilderImpl and we retrieve the current
+ // OverscrollSpec only when the transition implements HasOverscrollProperties, we can assume
+ // that this method was invoked after performing this check.
+ val overscrollProperties = transition as TransitionState.HasOverscrollProperties
+
+ return Offset(
+ x = value.x + overscrollProperties.overscrollScope.x(),
+ y = value.y + overscrollProperties.overscrollScope.y(),
+ )
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
index 33b57b2..73393a1 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
@@ -18,6 +18,7 @@
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TransitionState
+import kotlinx.coroutines.Job
/** A linked transition which is driven by a [originalTransition]. */
internal class LinkedTransition(
@@ -43,4 +44,6 @@
override val progress: Float
get() = originalTransition.progress
+
+ override fun finish(): Job = originalTransition.finish()
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index eb9b428..1e9a7e2 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -39,6 +39,7 @@
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.launch
import org.junit.Test
import org.junit.runner.RunWith
@@ -892,6 +893,50 @@
}
@Test
+ fun finish() = runGestureTest {
+ // Start at scene C.
+ navigateToSceneC()
+
+ // Swipe up from the middle to transition to scene B.
+ val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
+ onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+ assertTransition(fromScene = SceneC, toScene = SceneB, isUserInputOngoing = true)
+
+ // The current transition can be intercepted.
+ assertThat(draggableHandler.shouldImmediatelyIntercept(middle)).isTrue()
+
+ // Finish the transition.
+ val transition = transitionState as Transition
+ val job = transition.finish()
+ assertTransition(isUserInputOngoing = false)
+
+ // The current transition can not be intercepted anymore.
+ assertThat(draggableHandler.shouldImmediatelyIntercept(middle)).isFalse()
+
+ // Calling finish() multiple times returns the same Job.
+ assertThat(transition.finish()).isSameInstanceAs(job)
+ assertThat(transition.finish()).isSameInstanceAs(job)
+ assertThat(transition.finish()).isSameInstanceAs(job)
+
+ // We can join the job to wait for the animation to end.
+ assertTransition()
+ job.join()
+ assertIdle(SceneC)
+ }
+
+ @Test
+ fun finish_cancelled() = runGestureTest {
+ // Swipe up from the middle to transition to scene B.
+ val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
+ onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+ assertTransition(fromScene = SceneA, toScene = SceneB)
+
+ // Finish the transition and cancel the returned job.
+ (transitionState as Transition).finish().cancelAndJoin()
+ assertIdle(SceneA)
+ }
+
+ @Test
fun blockTransition() = runGestureTest {
assertIdle(SceneA)
@@ -951,4 +996,15 @@
assertThat(transition).isNotNull()
assertThat(transition!!.progress).isEqualTo(-0.1f)
}
+
+ @Test
+ fun transitionIsImmediatelyUpdatedWhenReleasingFinger() = runGestureTest {
+ // Swipe up from the middle to transition to scene B.
+ val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
+ val dragController = onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+ assertTransition(fromScene = SceneA, toScene = SceneB, isUserInputOngoing = true)
+
+ dragController.onDragStopped(velocity = 0f)
+ assertTransition(isUserInputOngoing = false)
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 059a10e..26e01fe 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -539,24 +539,20 @@
}
}
- @Test
- fun elementTransitionDuringOverscroll() {
+ private fun setupOverscrollScenario(
+ layoutWidth: Dp,
+ layoutHeight: Dp,
+ sceneTransitions: SceneTransitionsBuilder.() -> Unit,
+ firstScroll: Float
+ ): MutableSceneTransitionLayoutStateImpl {
// The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
// detected as a drag event.
var touchSlop = 0f
- val overscrollTranslateY = 10.dp
- val layoutWidth = 200.dp
- val layoutHeight = 400.dp
val state =
MutableSceneTransitionLayoutState(
initialScene = TestScenes.SceneA,
- transitions =
- transitions {
- overscroll(TestScenes.SceneB, Orientation.Vertical) {
- translate(TestElements.Foo, y = overscrollTranslateY)
- }
- }
+ transitions = transitions(sceneTransitions),
)
as MutableSceneTransitionLayoutStateImpl
@@ -585,9 +581,30 @@
rule.onRoot().performTouchInput {
val middleTop = Offset((layoutWidth / 2).toPx(), 0f)
down(middleTop)
- // Scroll 50%
- moveBy(Offset(0f, touchSlop + layoutHeight.toPx() * 0.5f), delayMillis = 1_000)
+ val firstScrollHeight = layoutHeight.toPx() * firstScroll
+ moveBy(Offset(0f, touchSlop + firstScrollHeight), delayMillis = 1_000)
}
+ return state
+ }
+
+ @Test
+ fun elementTransitionDuringOverscroll() {
+ val layoutWidth = 200.dp
+ val layoutHeight = 400.dp
+ val overscrollTranslateY = 10.dp
+
+ val state =
+ setupOverscrollScenario(
+ layoutWidth = layoutWidth,
+ layoutHeight = layoutHeight,
+ sceneTransitions = {
+ overscroll(TestScenes.SceneB, Orientation.Vertical) {
+ // On overscroll 100% -> Foo should translate by overscrollTranslateY
+ translate(TestElements.Foo, y = overscrollTranslateY)
+ }
+ },
+ firstScroll = 0.5f, // Scroll 50%
+ )
val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true)
fooElement.assertTopPositionInRootIsEqualTo(0.dp)
@@ -691,4 +708,48 @@
assertThat(state.currentOverscrollSpec).isNotNull()
fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 1.5f)
}
+
+ @Test
+ fun elementTransitionWithDistanceDuringOverscroll() {
+ val layoutWidth = 200.dp
+ val layoutHeight = 400.dp
+ val state =
+ setupOverscrollScenario(
+ layoutWidth = layoutWidth,
+ layoutHeight = layoutHeight,
+ sceneTransitions = {
+ overscroll(TestScenes.SceneB, Orientation.Vertical) {
+ // On overscroll 100% -> Foo should translate by layoutHeight
+ translate(TestElements.Foo, y = { absoluteDistance })
+ }
+ },
+ firstScroll = 1f, // 100% scroll
+ )
+
+ val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true)
+ fooElement.assertTopPositionInRootIsEqualTo(0.dp)
+
+ rule.onRoot().performTouchInput {
+ // Scroll another 50%
+ moveBy(Offset(0f, layoutHeight.toPx() * 0.5f), delayMillis = 1_000)
+ }
+
+ val transition = state.currentTransition
+ assertThat(transition).isNotNull()
+
+ // Scroll 150% (100% scroll + 50% overscroll)
+ assertThat(transition!!.progress).isEqualTo(1.5f)
+ assertThat(state.currentOverscrollSpec).isNotNull()
+ fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * 0.5f)
+
+ rule.onRoot().performTouchInput {
+ // Scroll another 100%
+ moveBy(Offset(0f, layoutHeight.toPx()), delayMillis = 1_000)
+ }
+
+ // Scroll 250% (100% scroll + 150% overscroll)
+ assertThat(transition.progress).isEqualTo(2.5f)
+ assertThat(state.currentOverscrollSpec).isNotNull()
+ fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * 1.5f)
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index 3cbcd73..9baabc3 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -28,6 +28,8 @@
import com.android.compose.test.runMonotonicClockTest
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.job
import kotlinx.coroutines.launch
import org.junit.Rule
import org.junit.Test
@@ -37,16 +39,6 @@
class SceneTransitionLayoutStateTest {
@get:Rule val rule = createComposeRule()
- class TestableTransition(
- fromScene: SceneKey,
- toScene: SceneKey,
- ) : TransitionState.Transition(fromScene, toScene) {
- override var currentScene: SceneKey = fromScene
- override var progress: Float = 0.0f
- override var isInitiatedByUserInput: Boolean = false
- override var isUserInputOngoing: Boolean = false
- }
-
@Test
fun isTransitioningTo_idle() {
val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
@@ -83,25 +75,31 @@
assertThat(transition).isNotNull()
assertThat(state.transitionState).isEqualTo(transition)
- testScheduler.advanceUntilIdle()
+ transition!!.finish().join()
assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
}
@Test
fun setTargetScene_transitionToSameScene() = runMonotonicClockTest {
val state = MutableSceneTransitionLayoutState(SceneA)
- assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
+
+ val transition = state.setTargetScene(SceneB, coroutineScope = this)
+ assertThat(transition).isNotNull()
assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNull()
- testScheduler.advanceUntilIdle()
+
+ transition!!.finish().join()
assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
}
@Test
fun setTargetScene_transitionToDifferentScene() = runMonotonicClockTest {
val state = MutableSceneTransitionLayoutState(SceneA)
+
assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
- assertThat(state.setTargetScene(SceneC, coroutineScope = this)).isNotNull()
- testScheduler.advanceUntilIdle()
+ val transition = state.setTargetScene(SceneC, coroutineScope = this)
+ assertThat(transition).isNotNull()
+
+ transition!!.finish().join()
assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneC))
}
@@ -127,11 +125,22 @@
assertThat(state.transitionState).isEqualTo(transition)
// Cancelling the scope/job still sets the state to Idle(targetScene).
- job.cancel()
- testScheduler.advanceUntilIdle()
+ job.cancelAndJoin()
assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
}
+ @Test
+ fun transition_finishReturnsTheSameJobWhenCalledMultipleTimes() = runMonotonicClockTest {
+ val state = MutableSceneTransitionLayoutState(SceneA)
+ val transition = state.setTargetScene(SceneB, coroutineScope = this)
+ assertThat(transition).isNotNull()
+
+ val job = transition!!.finish()
+ assertThat(transition.finish()).isSameInstanceAs(job)
+ assertThat(transition.finish()).isSameInstanceAs(job)
+ assertThat(transition.finish()).isSameInstanceAs(job)
+ }
+
private fun setupLinkedStates(
parentInitialScene: SceneKey = SceneC,
childInitialScene: SceneKey = SceneA,
@@ -159,7 +168,7 @@
fun linkedTransition_startsLinkAndFinishesLinkInToState() {
val (parentState, childState) = setupLinkedStates()
- val childTransition = TestableTransition(SceneA, SceneB)
+ val childTransition = transition(SceneA, SceneB)
childState.startTransition(childTransition, null)
assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
@@ -195,7 +204,7 @@
MutableSceneTransitionLayoutState(SceneA, stateLinks = link)
as BaseSceneTransitionLayoutState
- val childTransition = TestableTransition(SceneA, SceneB)
+ val childTransition = transition(SceneA, SceneB)
childState.startTransition(childTransition, null)
assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
@@ -212,12 +221,13 @@
fun linkedTransition_linkProgressIsEqual() {
val (parentState, childState) = setupLinkedStates()
- val childTransition = TestableTransition(SceneA, SceneB)
+ var progress = 0f
+ val childTransition = transition(SceneA, SceneB, progress = { progress })
childState.startTransition(childTransition, null)
assertThat(parentState.currentTransition?.progress).isEqualTo(0f)
- childTransition.progress = .5f
+ progress = .5f
assertThat(parentState.currentTransition?.progress).isEqualTo(.5f)
}
@@ -225,7 +235,7 @@
fun linkedTransition_reverseTransitionIsNotLinked() {
val (parentState, childState) = setupLinkedStates()
- val childTransition = TestableTransition(SceneB, SceneA)
+ val childTransition = transition(SceneB, SceneA)
childState.startTransition(childTransition, null)
assertThat(childState.isTransitioning(SceneB, SceneA)).isTrue()
@@ -240,7 +250,7 @@
fun linkedTransition_startsLinkAndFinishesLinkInFromState() {
val (parentState, childState) = setupLinkedStates()
- val childTransition = TestableTransition(SceneA, SceneB)
+ val childTransition = transition(SceneA, SceneB)
childState.startTransition(childTransition, null)
childState.finishTransition(childTransition, SceneA)
@@ -252,7 +262,7 @@
fun linkedTransition_startsLinkAndFinishesLinkInUnknownState() {
val (parentState, childState) = setupLinkedStates()
- val childTransition = TestableTransition(SceneA, SceneB)
+ val childTransition = transition(SceneA, SceneB)
childState.startTransition(childTransition, null)
childState.finishTransition(childTransition, SceneD)
@@ -264,8 +274,8 @@
fun linkedTransition_startsLinkButLinkedStateIsTakenOver() {
val (parentState, childState) = setupLinkedStates()
- val childTransition = TestableTransition(SceneA, SceneB)
- val parentTransition = TestableTransition(SceneC, SceneA)
+ val childTransition = transition(SceneA, SceneB)
+ val parentTransition = transition(SceneC, SceneA)
childState.startTransition(childTransition, null)
parentState.startTransition(parentTransition, null)
@@ -315,9 +325,9 @@
@Test
fun snapToIdleIfClose_snapToStart() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
+ val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
state.startTransition(
- transition(from = TestScenes.SceneA, to = TestScenes.SceneB, progress = { 0.2f }),
+ transition(from = SceneA, to = TestScenes.SceneB, progress = { 0.2f }),
transitionKey = null
)
assertThat(state.isTransitioning()).isTrue()
@@ -329,14 +339,14 @@
// Go to the initial scene if it is close to 0.
assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
assertThat(state.isTransitioning()).isFalse()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneA))
+ assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA))
}
@Test
fun snapToIdleIfClose_snapToEnd() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
+ val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
state.startTransition(
- transition(from = TestScenes.SceneA, to = TestScenes.SceneB, progress = { 0.8f }),
+ transition(from = SceneA, to = TestScenes.SceneB, progress = { 0.8f }),
transitionKey = null
)
assertThat(state.isTransitioning()).isTrue()
@@ -354,7 +364,7 @@
@Test
fun linkedTransition_fuzzyLinksAreMatchedAndStarted() {
val (parentState, childState) = setupLinkedStates(SceneC, SceneA, null, null, null, SceneD)
- val childTransition = TestableTransition(SceneA, SceneB)
+ val childTransition = transition(SceneA, SceneB)
childState.startTransition(childTransition, null)
assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
@@ -370,7 +380,7 @@
val (parentState, childState) =
setupLinkedStates(SceneC, SceneA, SceneA, null, null, SceneD)
- val childTransition = TestableTransition(SceneA, SceneB)
+ val childTransition = transition(SceneA, SceneB)
childState.startTransition(childTransition, null)
assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
@@ -385,7 +395,7 @@
fun linkedTransition_fuzzyLinksAreNotMatched() {
val (parentState, childState) =
setupLinkedStates(SceneC, SceneA, SceneB, null, SceneC, SceneD)
- val childTransition = TestableTransition(SceneA, SceneB)
+ val childTransition = transition(SceneA, SceneB)
childState.startTransition(childTransition, null)
assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
@@ -402,18 +412,12 @@
sceneTransitions,
)
state.startTransition(
- object :
- TransitionState.Transition(SceneA, SceneB),
- TransitionState.HasOverscrollProperties {
- override val currentScene: SceneKey = SceneA
- override val progress: Float
- get() = progress()
-
- override val isInitiatedByUserInput: Boolean = false
- override val isUserInputOngoing: Boolean = false
- override val isUpOrLeft: Boolean = false
- override val orientation: Orientation = Orientation.Vertical
- },
+ transition(
+ from = SceneA,
+ to = SceneB,
+ progress = progress,
+ orientation = Orientation.Vertical,
+ ),
transitionKey = null
)
assertThat(state.isTransitioning()).isTrue()
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
index c9c3ecc..825fe13 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -22,9 +22,9 @@
import androidx.compose.animation.core.tween
import androidx.compose.foundation.gestures.Orientation
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.transformation.OverscrollTranslate
import com.android.compose.animation.scene.transformation.Transformation
import com.android.compose.animation.scene.transformation.TransformationRange
-import com.android.compose.animation.scene.transformation.Translate
import com.google.common.truth.Correspondence
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -228,12 +228,14 @@
@Test
fun overscrollSpec() {
val transitions = transitions {
- overscroll(TestScenes.SceneA, Orientation.Vertical) { translate(TestElements.Bar) }
+ overscroll(TestScenes.SceneA, Orientation.Vertical) {
+ translate(TestElements.Bar, x = { 1f }, y = { 2f })
+ }
}
val overscrollSpec = transitions.overscrollSpecs.single()
val transformation = overscrollSpec.transformationSpec.transformations.single()
- assertThat(transformation).isInstanceOf(Translate::class.java)
+ assertThat(transformation).isInstanceOf(OverscrollTranslate::class.java)
}
companion object {
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt
index 238b21e1..73a66c6 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt
@@ -16,6 +16,9 @@
package com.android.compose.animation.scene
+import androidx.compose.foundation.gestures.Orientation
+import kotlinx.coroutines.Job
+
/** A utility to easily create a [TransitionState.Transition] in tests. */
fun transition(
from: SceneKey,
@@ -23,11 +26,25 @@
progress: () -> Float = { 0f },
isInitiatedByUserInput: Boolean = false,
isUserInputOngoing: Boolean = false,
+ isUpOrLeft: Boolean = false,
+ orientation: Orientation = Orientation.Horizontal,
): TransitionState.Transition {
- return object : TransitionState.Transition(from, to) {
+ return object : TransitionState.Transition(from, to), TransitionState.HasOverscrollProperties {
override val currentScene: SceneKey = from
- override val progress: Float = progress()
+ override val progress: Float
+ get() = progress()
+
override val isInitiatedByUserInput: Boolean = isInitiatedByUserInput
override val isUserInputOngoing: Boolean = isUserInputOngoing
+ override val isUpOrLeft: Boolean = isUpOrLeft
+ override val orientation: Orientation = orientation
+ override val overscrollScope: OverscrollScope =
+ object : OverscrollScope {
+ override val absoluteDistance = 0f
+ }
+
+ override fun finish(): Job {
+ error("finish() is not supported in test transitions")
+ }
}
}
diff --git a/packages/SystemUI/customization/res/values/ids.xml b/packages/SystemUI/customization/res/values/ids.xml
new file mode 100644
index 0000000..5eafbfc
--- /dev/null
+++ b/packages/SystemUI/customization/res/values/ids.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- View ids for elements in large weather clock -->
+ <item type="id" name="weather_clock_time" />
+ <item type="id" name="weather_clock_date" />
+ <item type="id" name="weather_clock_weather_icon" />
+ <item type="id" name="weather_clock_temperature" />
+ <item type="id" name="weather_clock_alarm_dnd" />
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
index 09fdd11..bd1403a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
@@ -36,6 +36,7 @@
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -201,6 +202,7 @@
@Test
fun verifySimPin() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(false)
whenever(telephonyManager.createForSubscriptionId(anyInt()))
@@ -208,8 +210,7 @@
whenever(telephonyManager.supplyIccLockPin(anyString()))
.thenReturn(PinResult(PinResult.PIN_RESULT_TYPE_SUCCESS, 1))
- val msg: String? = underTest.verifySim(listOf(0, 0, 0, 0))
- runCurrent()
+ verifySim(listOf(0, 0, 0, 0))
assertThat(msg).isNull()
verify(keyguardUpdateMonitor).reportSimUnlocked(1)
@@ -218,6 +219,7 @@
@Test
fun verifySimPin_incorrect_oneRemainingAttempt() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(false)
whenever(telephonyManager.createForSubscriptionId(anyInt()))
@@ -229,9 +231,7 @@
1,
)
)
-
- val msg: String? = underTest.verifySim(listOf(0, 0, 0, 0))
- runCurrent()
+ verifySim(listOf(0, 0, 0, 0))
assertThat(msg).isNull()
val errorDialogMessage by collectLastValue(bouncerSimRepository.errorDialogMessage)
@@ -245,6 +245,7 @@
@Test
fun verifySimPin_incorrect_threeRemainingAttempts() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(false)
whenever(telephonyManager.createForSubscriptionId(anyInt()))
@@ -257,8 +258,7 @@
)
)
- val msg = underTest.verifySim(listOf(0, 0, 0, 0))
- runCurrent()
+ verifySim(listOf(0, 0, 0, 0))
assertThat(msg).isEqualTo("Enter SIM PIN. You have 3 remaining attempts.")
}
@@ -266,10 +266,11 @@
@Test
fun verifySimPin_notCorrectLength_tooShort() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(false)
- val msg = underTest.verifySim(listOf(0))
+ verifySim(listOf(0))
assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_pin_hint))
}
@@ -277,10 +278,12 @@
@Test
fun verifySimPin_notCorrectLength_tooLong() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
+
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(false)
- val msg = underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
+ verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_pin_hint))
}
@@ -288,6 +291,7 @@
@Test
fun verifySimPuk() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
whenever(telephonyManager.createForSubscriptionId(anyInt()))
.thenReturn(telephonyManager)
whenever(telephonyManager.supplyIccLockPuk(anyString(), anyString()))
@@ -295,13 +299,13 @@
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(true)
- var msg = underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
+ verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
assertThat(msg).isEqualTo(resources.getString(R.string.kg_puk_enter_pin_hint))
- msg = underTest.verifySim(listOf(0, 0, 0, 0))
+ verifySim(listOf(0, 0, 0, 0))
assertThat(msg).isEqualTo(resources.getString(R.string.kg_enter_confirm_pin_hint))
- msg = underTest.verifySim(listOf(0, 0, 0, 0))
+ verifySim(listOf(0, 0, 0, 0))
assertThat(msg).isNull()
runCurrent()
@@ -311,37 +315,49 @@
@Test
fun verifySimPuk_inputTooShort() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
+
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(true)
- val msg = underTest.verifySim(listOf(0, 0, 0, 0))
+
+ verifySim(listOf(0, 0, 0, 0))
assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_puk_hint))
}
@Test
fun verifySimPuk_pinNotCorrectLength() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(true)
- underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
+ verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
- val msg = underTest.verifySim(listOf(0, 0, 0))
+ verifySim(listOf(0, 0, 0))
+
assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_pin_hint))
}
@Test
fun verifySimPuk_confirmedPinDoesNotMatch() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
+
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(true)
- underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
- underTest.verifySim(listOf(0, 0, 0, 0))
+ verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
+ verifySim(listOf(0, 0, 0, 0))
- val msg = underTest.verifySim(listOf(0, 0, 0, 1))
+ verifySim(listOf(0, 0, 0, 1))
assertThat(msg).isEqualTo(resources.getString(R.string.kg_puk_enter_pin_hint))
}
+ private suspend fun TestScope.verifySim(pinDigits: List<Int>) {
+ runCurrent()
+ underTest.verifySim(pinDigits)
+ }
+
@Test
fun onErrorDialogDismissed_clearsErrorDialogMessageInRepository() {
bouncerSimRepository.setSimVerificationErrorMessage("abc")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
new file mode 100644
index 0000000..def63ec
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
@@ -0,0 +1,132 @@
+/*
+ * 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
+
+import android.service.dream.dreamManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.power.shared.model.ScreenPowerState
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalDreamStartableTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private lateinit var underTest: CommunalDreamStartable
+
+ private val dreamManager by lazy { kosmos.dreamManager }
+ private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+ private val powerRepository by lazy { kosmos.fakePowerRepository }
+
+ @Before
+ fun setUp() {
+ underTest =
+ CommunalDreamStartable(
+ powerInteractor = kosmos.powerInteractor,
+ keyguardInteractor = kosmos.keyguardInteractor,
+ keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
+ dreamManager = dreamManager,
+ bgScope = kosmos.applicationCoroutineScope,
+ )
+ .apply { start() }
+ }
+
+ @Test
+ fun startDreamWhenTransitioningToHub() =
+ testScope.runTest {
+ keyguardRepository.setDreaming(false)
+ powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+ whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(true)
+ runCurrent()
+
+ verify(dreamManager, never()).startDream()
+
+ transition(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB)
+
+ verify(dreamManager).startDream()
+ }
+
+ @Test
+ fun shouldNotStartDreamWhenIneligibleToDream() =
+ testScope.runTest {
+ keyguardRepository.setDreaming(false)
+ powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+ // Not eligible to dream
+ whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(false)
+ transition(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB)
+
+ verify(dreamManager, never()).startDream()
+ }
+
+ @Test
+ fun shouldNotStartDreamIfAlreadyDreaming() =
+ testScope.runTest {
+ keyguardRepository.setDreaming(true)
+ powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+ whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(true)
+ transition(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB)
+
+ verify(dreamManager, never()).startDream()
+ }
+
+ @Test
+ fun shouldNotStartDreamForInvalidTransition() =
+ testScope.runTest {
+ keyguardRepository.setDreaming(true)
+ powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+ whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(true)
+
+ // Verify we do not trigger dreaming for any other state besides glanceable hub
+ for (state in KeyguardState.entries) {
+ if (state == KeyguardState.GLANCEABLE_HUB) continue
+ transition(from = KeyguardState.GLANCEABLE_HUB, to = state)
+ verify(dreamManager, never()).startDream()
+ }
+ }
+
+ private suspend fun TestScope.transition(from: KeyguardState, to: KeyguardState) {
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = from,
+ to = to,
+ testScope = this
+ )
+ runCurrent()
+ }
+}
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 eafd503..a5707e1 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
@@ -19,8 +19,12 @@
import android.app.smartspace.SmartspaceTarget
import android.appwidget.AppWidgetProviderInfo
+import android.content.Intent
import android.content.pm.UserInfo
import android.os.UserHandle
+import android.os.UserManager
+import android.os.userManager
+import android.provider.Settings
import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
import android.widget.RemoteViews
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -50,6 +54,8 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.activityStarter
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
@@ -61,6 +67,9 @@
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.fakeSettings
@@ -73,6 +82,8 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
@@ -103,6 +114,8 @@
private lateinit var editWidgetsActivityStarter: EditWidgetsActivityStarter
private lateinit var sceneInteractor: SceneInteractor
private lateinit var userTracker: FakeUserTracker
+ private lateinit var activityStarter: ActivityStarter
+ private lateinit var userManager: UserManager
private lateinit var underTest: CommunalInteractor
@@ -121,9 +134,13 @@
communalPrefsRepository = kosmos.fakeCommunalPrefsRepository
sceneInteractor = kosmos.sceneInteractor
userTracker = kosmos.fakeUserTracker
+ activityStarter = kosmos.activityStarter
+ userManager = kosmos.userManager
whenever(mainUser.isMain).thenReturn(true)
whenever(secondaryUser.isMain).thenReturn(false)
+ whenever(userManager.isQuietModeEnabled(any<UserHandle>())).thenReturn(false)
+ whenever(userManager.isManagedProfile(anyInt())).thenReturn(false)
userRepository.setUserInfos(listOf(mainUser, secondaryUser))
kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
@@ -800,6 +817,16 @@
}
@Test
+ fun navigateToCommunalWidgetSettings_startsActivity() =
+ testScope.runTest {
+ underTest.navigateToCommunalWidgetSettings()
+ val intentCaptor = argumentCaptor<Intent>()
+ verify(activityStarter)
+ .postStartActivityDismissingKeyguard(capture(intentCaptor), eq(0))
+ assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_COMMUNAL_SETTING)
+ }
+
+ @Test
fun filterWidgets_whenUserProfileRemoved() =
testScope.runTest {
// Keyguard showing, and tutorial completed.
@@ -832,6 +859,63 @@
}
@Test
+ fun widgetContent_inQuietMode() =
+ testScope.runTest {
+ // Keyguard showing, and tutorial completed.
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setKeyguardOccluded(false)
+ tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+
+ // Work profile is set up.
+ val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
+ userRepository.setUserInfos(userInfos)
+ userTracker.set(
+ userInfos = userInfos,
+ selectedUserIndex = 0,
+ )
+ runCurrent()
+
+ // Keyguard widgets are allowed.
+ kosmos.fakeSettings.putIntForUser(
+ CommunalSettingsRepositoryImpl.GLANCEABLE_HUB_CONTENT_SETTING,
+ AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
+ mainUser.id
+ )
+ runCurrent()
+
+ // When work profile is paused.
+ whenever(userManager.isQuietModeEnabled(eq(UserHandle.of(USER_INFO_WORK.id))))
+ .thenReturn(true)
+ whenever(userManager.isManagedProfile(eq(USER_INFO_WORK.id))).thenReturn(true)
+
+ val widgetContent by collectLastValue(underTest.widgetContent)
+ val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
+ val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id)
+ val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
+ val widgets = listOf(widget1, widget2, widget3)
+ widgetRepository.setCommunalWidgets(widgets)
+
+ // The work profile widget is in quiet mode, while other widgets are not.
+ assertThat(widgetContent).hasSize(3)
+ widgetContent!!.forEach { model ->
+ assertThat(model)
+ .isInstanceOf(CommunalContentModel.WidgetContent.Widget::class.java)
+ }
+ assertThat(
+ (widgetContent!![0] as CommunalContentModel.WidgetContent.Widget).inQuietMode
+ )
+ .isTrue()
+ assertThat(
+ (widgetContent!![1] as CommunalContentModel.WidgetContent.Widget).inQuietMode
+ )
+ .isFalse()
+ assertThat(
+ (widgetContent!![2] as CommunalContentModel.WidgetContent.Widget).inQuietMode
+ )
+ .isFalse()
+ }
+
+ @Test
fun widgetContent_containsDisabledWidgets_whenCategoryNotAllowed() =
testScope.runTest {
// Communal available, and tutorial completed.
@@ -932,7 +1016,10 @@
private fun createWidgetForUser(appWidgetId: Int, userId: Int): CommunalWidgetContentModel =
mock<CommunalWidgetContentModel> {
whenever(this.appWidgetId).thenReturn(appWidgetId)
- val providerInfo = mock<AppWidgetProviderInfo>()
+ val providerInfo =
+ mock<AppWidgetProviderInfo>().apply {
+ widgetCategory = AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD
+ }
whenever(providerInfo.profile).thenReturn(UserHandle(userId))
whenever(this.providerInfo).thenReturn(providerInfo)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorTest.kt
index 2e9ee5c..4a7757b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorTest.kt
@@ -21,6 +21,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -35,6 +36,7 @@
private val kosmos = testKosmos()
private val biometricSettingsRepository = kosmos.biometricSettingsRepository
private val underTest = kosmos.deviceEntryBiometricSettingsInteractor
+ private val testScope = kosmos.testScope
@Test
fun isCoex_true() = runTest {
@@ -59,4 +61,25 @@
biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
assertThat(isCoex).isFalse()
}
+
+ @Test
+ fun authenticationFlags_providesAuthFlagsFromRepository() =
+ testScope.runTest {
+ assertThat(underTest.authenticationFlags)
+ .isSameInstanceAs(biometricSettingsRepository.authenticationFlags)
+ }
+
+ @Test
+ fun isFaceAuthEnrolledAndEnabled_providesValueFromRepository() =
+ testScope.runTest {
+ assertThat(underTest.isFaceAuthEnrolledAndEnabled)
+ .isSameInstanceAs(biometricSettingsRepository.isFaceAuthEnrolledAndEnabled)
+ }
+
+ @Test
+ fun isFingerprintAuthEnrolledAndEnabled_providesValueFromRepository() =
+ testScope.runTest {
+ assertThat(underTest.isFingerprintAuthEnrolledAndEnabled)
+ .isSameInstanceAs(biometricSettingsRepository.isFingerprintEnrolledAndEnabled)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index 4f44705..70ceb2a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -19,6 +19,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.SceneKey
+import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -27,8 +28,21 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.AdaptiveAuthRequest
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.BouncerLockedOut
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.PolicyLockdown
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.SecurityTimeout
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.TrustAgentDisabled
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UnattendedUpdate
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UserLockdown
+import com.android.systemui.flags.fakeSystemPropertiesHelper
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeTrustRepository
+import com.android.systemui.keyguard.shared.model.AuthenticationFlags
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
@@ -36,6 +50,7 @@
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -230,8 +245,8 @@
assertThat(canSwipeToEnter).isFalse()
trustRepository.setCurrentUserTrusted(true)
- runCurrent()
faceAuthRepository.isAuthenticated.value = false
+ runCurrent()
assertThat(canSwipeToEnter).isTrue()
}
@@ -383,6 +398,204 @@
assertThat(isUnlocked).isTrue()
}
+ @Test
+ fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_alwaysNull() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to null,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to null,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to null,
+ LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to null,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+ null,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ null,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to null
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenFaceIsEnrolledAndEnabled_mapsToAuthFlagsState() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ kosmos.fakeSystemPropertiesHelper.set(
+ DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
+ "not mainline reboot"
+ )
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
+ DeviceNotUnlockedSinceReboot,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
+ AdaptiveAuthRequest,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
+ BouncerLockedOut,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+ SecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+ UserLockdown,
+ LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+ NonStrongBiometricsSecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ UnattendedUpdate,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+ PolicyLockdown,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+ null,
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenFingerprintIsEnrolledAndEnabled_mapsToAuthFlagsState() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ kosmos.fakeSystemPropertiesHelper.set(
+ DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
+ "not mainline reboot"
+ )
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
+ DeviceNotUnlockedSinceReboot,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
+ AdaptiveAuthRequest,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
+ BouncerLockedOut,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+ SecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+ UserLockdown,
+ LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+ NonStrongBiometricsSecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ UnattendedUpdate,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+ PolicyLockdown,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+ null,
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenTrustAgentIsEnabled_mapsToAuthFlagsState() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(true)
+ kosmos.fakeTrustRepository.setCurrentUserTrustManaged(false)
+ kosmos.fakeSystemPropertiesHelper.set(
+ DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
+ "not mainline reboot"
+ )
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
+ DeviceNotUnlockedSinceReboot,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
+ AdaptiveAuthRequest,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
+ BouncerLockedOut,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+ SecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+ UserLockdown,
+ LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+ NonStrongBiometricsSecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ UnattendedUpdate,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+ PolicyLockdown,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to
+ TrustAgentDisabled,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+ TrustAgentDisabled,
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenDeviceRebootedForMainlineUpdate_mapsToTheCorrectReason() =
+ testScope.runTest {
+ val deviceEntryRestrictionReason by
+ collectLastValue(underTest.deviceEntryRestrictionReason)
+ kosmos.fakeSystemPropertiesHelper.set(
+ DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
+ DeviceEntryInteractor.REBOOT_MAINLINE_UPDATE
+ )
+ kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
+ AuthenticationFlags(
+ userId = 1,
+ flag = LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+ )
+ )
+ runCurrent()
+
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ runCurrent()
+
+ assertThat(deviceEntryRestrictionReason).isNull()
+
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+ runCurrent()
+
+ assertThat(deviceEntryRestrictionReason)
+ .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
+
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ runCurrent()
+
+ assertThat(deviceEntryRestrictionReason)
+ .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
+
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(true)
+ runCurrent()
+
+ assertThat(deviceEntryRestrictionReason)
+ .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
+ }
+
+ private fun TestScope.verifyRestrictionReasonsForAuthFlags(
+ vararg authFlagToDeviceEntryRestriction: Pair<Int, DeviceEntryRestrictionReason?>
+ ) {
+ val deviceEntryRestrictionReason by collectLastValue(underTest.deviceEntryRestrictionReason)
+
+ authFlagToDeviceEntryRestriction.forEach { (flag, expectedReason) ->
+ kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
+ AuthenticationFlags(userId = 1, flag = flag)
+ )
+ runCurrent()
+
+ if (expectedReason == null) {
+ assertThat(deviceEntryRestrictionReason).isNull()
+ } else {
+ assertThat(deviceEntryRestrictionReason).isEqualTo(expectedReason)
+ }
+ }
+ }
+
private fun switchToScene(sceneKey: SceneKey) {
sceneInteractor.changeScene(sceneKey, "reason")
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
index c670506..86fdaa5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -8,7 +8,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.complication.ComplicationHostViewController
-import com.android.systemui.dreams.ui.viewmodel.DreamOverlayViewModel
+import com.android.systemui.dreams.ui.viewmodel.DreamViewModel
import com.android.systemui.log.core.FakeLogBuffer
import com.android.systemui.statusbar.BlurUtils
import com.android.systemui.util.mockito.argumentCaptor
@@ -45,7 +45,7 @@
@Mock private lateinit var hostViewController: ComplicationHostViewController
@Mock private lateinit var statusBarViewController: DreamOverlayStatusBarViewController
@Mock private lateinit var stateController: DreamOverlayStateController
- @Mock private lateinit var transitionViewModel: DreamOverlayViewModel
+ @Mock private lateinit var transitionViewModel: DreamViewModel
private val logBuffer = FakeLogBuffer.Factory.create()
private lateinit var controller: DreamOverlayAnimationsController
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
index 87b1bbb..1adf414 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
@@ -40,7 +40,6 @@
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import java.util.Optional
@@ -50,7 +49,6 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -136,12 +134,17 @@
@Test
@DisableFlags(FLAG_HOME_PANEL_DREAM)
- fun testStartDoesNotRunDreamServiceWhenFlagIsDisabled() =
+ fun testStartDisablesDreamServiceWhenFlagIsDisabled() =
testScope.runTest {
selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_NON_PANEL)
startable.start()
runCurrent()
- verify(packageManager, never()).setComponentEnabledSetting(any(), any(), any())
+ verify(packageManager)
+ .setComponentEnabledSetting(
+ eq(componentName),
+ eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
+ eq(PackageManager.DONT_KILL_APP)
+ )
}
private fun ControlsServiceInfo(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
new file mode 100644
index 0000000..8f03717
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
@@ -0,0 +1,332 @@
+/*
+ * 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.haptics.qs
+
+import android.os.VibrationEffect
+import android.testing.TestableLooper.RunWithLooper
+import android.view.MotionEvent
+import android.view.View
+import androidx.test.core.view.MotionEventBuilder
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.AnimatorTestRule
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWithLooper(setAsMainLooper = true)
+class QSLongPressEffectTest : SysuiTestCase() {
+
+ @Rule @JvmField val mMockitoRule: MockitoRule = MockitoJUnit.rule()
+ @Mock private lateinit var vibratorHelper: VibratorHelper
+ @Mock private lateinit var testView: View
+ @get:Rule val animatorTestRule = AnimatorTestRule(this)
+ private val kosmos = testKosmos()
+
+ private val effectDuration = 400
+ private val lowTickDuration = 12
+ private val spinDuration = 133
+
+ private lateinit var longPressEffect: QSLongPressEffect
+
+ @Before
+ fun setup() {
+ whenever(
+ vibratorHelper.getPrimitiveDurations(
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+ VibrationEffect.Composition.PRIMITIVE_SPIN,
+ )
+ )
+ .thenReturn(intArrayOf(lowTickDuration, spinDuration))
+
+ longPressEffect =
+ QSLongPressEffect(
+ vibratorHelper,
+ effectDuration,
+ )
+ }
+
+ @Test
+ fun onActionDown_whileIdle_startsWait() = testWithScope {
+ // GIVEN an action down event occurs
+ val downEvent = buildMotionEvent(MotionEvent.ACTION_DOWN)
+ longPressEffect.onTouch(testView, downEvent)
+
+ // THEN the effect moves to the TIMEOUT_WAIT state
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.TIMEOUT_WAIT)
+ }
+
+ @Test
+ fun onActionCancel_whileWaiting_goesIdle() = testWhileWaiting {
+ // GIVEN an action cancel occurs
+ val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
+ longPressEffect.onTouch(testView, cancelEvent)
+
+ // THEN the effect goes back to idle and does not start
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
+ assertEffectDidNotStart()
+ }
+
+ @Test
+ fun onActionUp_whileWaiting_performsClick() = testWhileWaiting {
+ // GIVEN an action is being collected
+ val action by collectLastValue(longPressEffect.actionType)
+
+ // GIVEN an action up occurs
+ val upEvent = buildMotionEvent(MotionEvent.ACTION_UP)
+ longPressEffect.onTouch(testView, upEvent)
+
+ // THEN the action to invoke is the click action and the effect does not start
+ assertThat(action).isEqualTo(QSLongPressEffect.ActionType.CLICK)
+ assertEffectDidNotStart()
+ }
+
+ @Test
+ fun onWaitComplete_whileWaiting_beginsEffect() = testWhileWaiting {
+ // GIVEN the pressed timeout is complete
+ advanceTimeBy(QSLongPressEffect.PRESSED_TIMEOUT + 10L)
+
+ // THEN the effect starts
+ assertEffectStarted()
+ }
+
+ @Test
+ fun onActionUp_whileEffectHasBegun_reversesEffect() = testWhileRunning {
+ // GIVEN that the effect is at the middle of its completion (progress of 50%)
+ animatorTestRule.advanceTimeBy(effectDuration / 2L)
+
+ // WHEN an action up occurs
+ val upEvent = buildMotionEvent(MotionEvent.ACTION_UP)
+ longPressEffect.onTouch(testView, upEvent)
+
+ // THEN the effect gets reversed at 50% progress
+ assertEffectReverses(0.5f)
+ }
+
+ @Test
+ fun onActionCancel_whileEffectHasBegun_reversesEffect() = testWhileRunning {
+ // GIVEN that the effect is at the middle of its completion (progress of 50%)
+ animatorTestRule.advanceTimeBy(effectDuration / 2L)
+
+ // WHEN an action cancel occurs
+ val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
+ longPressEffect.onTouch(testView, cancelEvent)
+
+ // THEN the effect gets reversed at 50% progress
+ assertEffectReverses(0.5f)
+ }
+
+ @Test
+ fun onAnimationComplete_effectEnds() = testWhileRunning {
+ // GIVEN that the animation completes
+ animatorTestRule.advanceTimeBy(effectDuration + 10L)
+
+ // THEN the long-press effect completes
+ assertEffectCompleted()
+ }
+
+ @Test
+ fun onActionDown_whileRunningBackwards_resets() = testWhileRunning {
+ // GIVEN that the effect is at the middle of its completion (progress of 50%)
+ animatorTestRule.advanceTimeBy(effectDuration / 2L)
+
+ // GIVEN an action cancel occurs and the effect gets reversed
+ val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
+ longPressEffect.onTouch(testView, cancelEvent)
+
+ // GIVEN an action down occurs
+ val downEvent = buildMotionEvent(MotionEvent.ACTION_DOWN)
+ longPressEffect.onTouch(testView, downEvent)
+
+ // THEN the effect resets
+ assertEffectResets()
+ }
+
+ @Test
+ fun onAnimationComplete_whileRunningBackwards_goesToIdle() = testWhileRunning {
+ // GIVEN that the effect is at the middle of its completion (progress of 50%)
+ animatorTestRule.advanceTimeBy(effectDuration / 2L)
+
+ // GIVEN an action cancel occurs and the effect gets reversed
+ val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
+ longPressEffect.onTouch(testView, cancelEvent)
+
+ // GIVEN that the animation completes after a sufficient amount of time
+ animatorTestRule.advanceTimeBy(effectDuration.toLong())
+
+ // THEN the state goes to [QSLongPressEffect.State.IDLE]
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
+ }
+
+ private fun buildMotionEvent(action: Int): MotionEvent =
+ MotionEventBuilder.newBuilder().setAction(action).build()
+
+ private fun testWithScope(test: suspend TestScope.() -> Unit) =
+ with(kosmos) {
+ testScope.runTest {
+ // GIVEN an effect with a testing scope
+ longPressEffect.scope = CoroutineScope(UnconfinedTestDispatcher(testScheduler))
+
+ // THEN run the test
+ test()
+ }
+ }
+
+ private fun testWhileWaiting(test: suspend TestScope.() -> Unit) =
+ with(kosmos) {
+ testScope.runTest {
+ // GIVEN an effect with a testing scope
+ longPressEffect.scope = CoroutineScope(UnconfinedTestDispatcher(testScheduler))
+
+ // GIVEN the TIMEOUT_WAIT state is entered
+ val downEvent =
+ MotionEventBuilder.newBuilder().setAction(MotionEvent.ACTION_DOWN).build()
+ longPressEffect.onTouch(testView, downEvent)
+
+ // THEN run the test
+ test()
+ }
+ }
+
+ private fun testWhileRunning(test: suspend TestScope.() -> Unit) =
+ with(kosmos) {
+ testScope.runTest {
+ // GIVEN an effect with a testing scope
+ longPressEffect.scope = CoroutineScope(UnconfinedTestDispatcher(testScheduler))
+
+ // GIVEN the down event that enters the TIMEOUT_WAIT state
+ val downEvent =
+ MotionEventBuilder.newBuilder().setAction(MotionEvent.ACTION_DOWN).build()
+ longPressEffect.onTouch(testView, downEvent)
+
+ // GIVEN that the timeout completes and the effect starts
+ advanceTimeBy(QSLongPressEffect.PRESSED_TIMEOUT + 10L)
+
+ // THEN run the test
+ test()
+ }
+ }
+
+ /**
+ * Asserts that the effect started by checking that:
+ * 1. The effect progress is 0f
+ * 2. Initial hint haptics are played
+ * 3. The internal state is [QSLongPressEffect.State.RUNNING_FORWARD]
+ */
+ private fun TestScope.assertEffectStarted() {
+ val effectProgress by collectLastValue(longPressEffect.effectProgress)
+ val longPressHint =
+ LongPressHapticBuilder.createLongPressHint(
+ lowTickDuration,
+ spinDuration,
+ effectDuration,
+ )
+
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.RUNNING_FORWARD)
+ assertThat(effectProgress).isEqualTo(0f)
+ assertThat(longPressHint).isNotNull()
+ verify(vibratorHelper).vibrate(longPressHint!!)
+ }
+
+ /**
+ * Asserts that the effect did not start by checking that:
+ * 1. No effect progress is emitted
+ * 2. No haptics are played
+ * 3. The internal state is not [QSLongPressEffect.State.RUNNING_BACKWARDS] or
+ * [QSLongPressEffect.State.RUNNING_FORWARD]
+ */
+ private fun TestScope.assertEffectDidNotStart() {
+ val effectProgress by collectLastValue(longPressEffect.effectProgress)
+
+ assertThat(longPressEffect.state).isNotEqualTo(QSLongPressEffect.State.RUNNING_FORWARD)
+ assertThat(longPressEffect.state).isNotEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS)
+ assertThat(effectProgress).isNull()
+ verify(vibratorHelper, never()).vibrate(any(/* type= */ VibrationEffect::class.java))
+ }
+
+ /**
+ * Asserts that the effect completes by checking that:
+ * 1. The progress is null
+ * 2. The final snap haptics are played
+ * 3. The internal state goes back to [QSLongPressEffect.State.IDLE]
+ * 4. The action to perform on the tile is the long-press action
+ */
+ private fun TestScope.assertEffectCompleted() {
+ val action by collectLastValue(longPressEffect.actionType)
+ val effectProgress by collectLastValue(longPressEffect.effectProgress)
+ val snapEffect = LongPressHapticBuilder.createSnapEffect()
+
+ assertThat(effectProgress).isNull()
+ assertThat(snapEffect).isNotNull()
+ verify(vibratorHelper).vibrate(snapEffect!!)
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
+ assertThat(action).isEqualTo(QSLongPressEffect.ActionType.LONG_PRESS)
+ }
+
+ /**
+ * Assert that the effect gets reverted by checking that:
+ * 1. The internal state is [QSLongPressEffect.State.RUNNING_BACKWARDS]
+ * 2. The reverse haptics plays at the point where the animation was paused
+ */
+ private fun assertEffectReverses(pausedProgress: Float) {
+ val reverseHaptics =
+ LongPressHapticBuilder.createReversedEffect(
+ pausedProgress,
+ lowTickDuration,
+ effectDuration,
+ )
+
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS)
+ assertThat(reverseHaptics).isNotNull()
+ verify(vibratorHelper).vibrate(reverseHaptics!!)
+ }
+
+ /**
+ * Asserts that the effect resets by checking that:
+ * 1. The effect progress resets to 0
+ * 2. The internal state goes back to [QSLongPressEffect.State.TIMEOUT_WAIT]
+ */
+ private fun TestScope.assertEffectResets() {
+ val effectProgress by collectLastValue(longPressEffect.effectProgress)
+ assertThat(effectProgress).isEqualTo(0f)
+
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.TIMEOUT_WAIT)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index 9da34da..e34edb4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -26,14 +26,9 @@
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
-import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
-import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
-import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -219,37 +214,6 @@
values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) }
}
- @Test
- fun transitionEnded() =
- testScope.runTest {
- val values by collectValues(underTest.transitionEnded)
-
- keyguardTransitionRepository.sendTransitionSteps(
- listOf(
- TransitionStep(DOZING, DREAMING, 0.0f, STARTED),
- TransitionStep(DOZING, DREAMING, 1.0f, FINISHED),
- TransitionStep(DREAMING, LOCKSCREEN, 0.0f, STARTED),
- TransitionStep(DREAMING, LOCKSCREEN, 0.1f, RUNNING),
- TransitionStep(DREAMING, LOCKSCREEN, 1.0f, FINISHED),
- TransitionStep(LOCKSCREEN, DREAMING, 0.0f, STARTED),
- TransitionStep(LOCKSCREEN, DREAMING, 0.5f, RUNNING),
- TransitionStep(LOCKSCREEN, DREAMING, 1.0f, FINISHED),
- TransitionStep(DREAMING, GONE, 0.0f, STARTED),
- TransitionStep(DREAMING, GONE, 0.5f, RUNNING),
- TransitionStep(DREAMING, GONE, 1.0f, CANCELED),
- TransitionStep(DREAMING, AOD, 0.0f, STARTED),
- TransitionStep(DREAMING, AOD, 1.0f, FINISHED),
- ),
- testScope,
- )
-
- assertThat(values.size).isEqualTo(3)
- values.forEach {
- assertThat(it.transitionState == FINISHED || it.transitionState == CANCELED)
- .isTrue()
- }
- }
-
private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep {
return TransitionStep(
from = DREAMING,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 19950a5..2fd2ef1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -19,9 +19,12 @@
package com.android.systemui.keyguard.ui.viewmodel
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.compose.animation.scene.Edge
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
@@ -31,86 +34,129 @@
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.kosmos.testScope
-import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.domain.startable.shadeStartable
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
+import platform.test.runner.parameterized.Parameter
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(ParameterizedAndroidJunit4::class)
class LockscreenSceneViewModelTest : SysuiTestCase() {
+ companion object {
+ @Parameters(
+ name =
+ "canSwipeToEnter={0}, downWithTwoPointers={1}, downFromEdge={2}," +
+ " isSingleShade={3}, isCommunalAvailable={4}"
+ )
+ @JvmStatic
+ fun combinations() = buildList {
+ repeat(32) { combination ->
+ add(
+ arrayOf(
+ /* canSwipeToEnter= */ combination and 1 != 0,
+ /* downWithTwoPointers= */ combination and 2 != 0,
+ /* downFromEdge= */ combination and 4 != 0,
+ /* isSingleShade= */ combination and 8 != 0,
+ /* isCommunalAvailable= */ combination and 16 != 0,
+ )
+ )
+ }
+ }
+
+ @JvmStatic
+ @BeforeClass
+ fun setUp() {
+ val combinationStrings =
+ combinations().map { array ->
+ check(array.size == 5)
+ "${array[4]},${array[3]},${array[2]},${array[1]},${array[0]}"
+ }
+ val uniqueCombinations = combinationStrings.toSet()
+ assertThat(combinationStrings).hasSize(uniqueCombinations.size)
+ }
+
+ private fun expectedDownDestination(
+ downFromEdge: Boolean,
+ isSingleShade: Boolean,
+ ): SceneKey {
+ return if (downFromEdge && isSingleShade) Scenes.QuickSettings else Scenes.Shade
+ }
+ }
+
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ @JvmField @Parameter(0) var canSwipeToEnter: Boolean = false
+ @JvmField @Parameter(1) var downWithTwoPointers: Boolean = false
+ @JvmField @Parameter(2) var downFromEdge: Boolean = false
+ @JvmField @Parameter(3) var isSingleShade: Boolean = true
+ @JvmField @Parameter(4) var isCommunalAvailable: Boolean = false
+
private val underTest by lazy { createLockscreenSceneViewModel() }
@Test
- fun upTransitionSceneKey_canSwipeToUnlock_gone() =
+ @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
+ fun destinationScenes() =
testScope.runTest {
- val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.None
- )
kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
- sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
-
- assertThat(upTransitionSceneKey).isEqualTo(Scenes.Gone)
- }
-
- @Test
- fun upTransitionSceneKey_cannotSwipeToUnlock_bouncer() =
- testScope.runTest {
- val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin
+ if (canSwipeToEnter) {
+ AuthenticationMethodModel.None
+ } else {
+ AuthenticationMethodModel.Pin
+ }
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(canSwipeToEnter)
sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
+ kosmos.shadeRepository.setShadeMode(
+ if (isSingleShade) {
+ ShadeMode.Single
+ } else {
+ ShadeMode.Split
+ }
+ )
+ kosmos.setCommunalAvailable(isCommunalAvailable)
- assertThat(upTransitionSceneKey).isEqualTo(Scenes.Bouncer)
- }
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
- @EnableFlags(FLAG_COMMUNAL_HUB)
- @Test
- fun leftTransitionSceneKey_communalIsAvailable_communal() =
- testScope.runTest {
- val leftDestinationSceneKey by collectLastValue(underTest.leftDestinationSceneKey)
- assertThat(leftDestinationSceneKey).isNull()
+ assertThat(
+ destinationScenes
+ ?.get(
+ Swipe(
+ SwipeDirection.Down,
+ fromSource = Edge.Top.takeIf { downFromEdge },
+ pointerCount = if (downWithTwoPointers) 2 else 1,
+ )
+ )
+ ?.toScene
+ )
+ .isEqualTo(
+ expectedDownDestination(
+ downFromEdge = downFromEdge,
+ isSingleShade = isSingleShade,
+ )
+ )
- kosmos.setCommunalAvailable(true)
- runCurrent()
- assertThat(leftDestinationSceneKey).isEqualTo(Scenes.Communal)
- }
+ assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
+ .isEqualTo(if (canSwipeToEnter) Scenes.Gone else Scenes.Bouncer)
- @Test
- fun downFromTopEdgeDestinationSceneKey_whenNotSplitShade_quickSettings() =
- testScope.runTest {
- overrideResource(R.bool.config_use_split_notification_shade, false)
- kosmos.shadeStartable.start()
- val sceneKey by collectLastValue(underTest.downFromTopEdgeDestinationSceneKey)
- assertThat(sceneKey).isEqualTo(Scenes.QuickSettings)
- }
-
- @Test
- fun downFromTopEdgeDestinationSceneKey_whenSplitShade_null() =
- testScope.runTest {
- overrideResource(R.bool.config_use_split_notification_shade, true)
- kosmos.shadeStartable.start()
- val sceneKey by collectLastValue(underTest.downFromTopEdgeDestinationSceneKey)
- assertThat(sceneKey).isNull()
+ assertThat(destinationScenes?.get(Swipe(SwipeDirection.Left))?.toScene)
+ .isEqualTo(Scenes.Communal.takeIf { isCommunalAvailable })
}
private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
index a1f885c..c0e5a9b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
@@ -114,7 +114,7 @@
DisabledByPolicyInteractor.PolicyResult.TileDisabled(ADMIN)
)
- val expectedIntent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(context, ADMIN)
+ val expectedIntent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(ADMIN)
assertThat(result).isTrue()
verify(activityStarter).postStartActivityDismissingKeyguard(intentCaptor.capture(), any())
assertThat(intentCaptor.value.filterEquals(expectedIntent)).isTrue()
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 42c3354..af9abcd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -26,7 +26,6 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
import com.android.internal.R
import com.android.internal.util.EmergencyAffordanceManager
import com.android.internal.util.emergencyAffordanceManager
@@ -317,8 +316,8 @@
@Test
fun swipeUpOnLockscreen_enterCorrectPin_unlocksDevice() =
testScope.runTest {
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(
to = upDestinationSceneKey,
@@ -337,8 +336,8 @@
testScope.runTest {
setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
emulateUserDrivenTransition(
to = upDestinationSceneKey,
@@ -356,7 +355,7 @@
emulateUserDrivenTransition(to = Scenes.Shade)
assertCurrentScene(Scenes.Shade)
- val upDestinationSceneKey = destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Lockscreen)
emulateUserDrivenTransition(
to = upDestinationSceneKey,
@@ -379,7 +378,7 @@
emulateUserDrivenTransition(to = Scenes.Shade)
assertCurrentScene(Scenes.Shade)
- val upDestinationSceneKey = destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
emulateUserDrivenTransition(
to = upDestinationSceneKey,
@@ -447,8 +446,8 @@
fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() =
testScope.runTest {
unlockDevice()
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
}
@@ -469,8 +468,8 @@
fun dismissingIme_whileOnPasswordBouncer_navigatesToLockscreen() =
testScope.runTest {
setAuthMethod(AuthenticationMethodModel.Password)
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(
to = upDestinationSceneKey,
@@ -487,8 +486,8 @@
fun bouncerActionButtonClick_opensEmergencyServicesDialer() =
testScope.runTest {
setAuthMethod(AuthenticationMethodModel.Password)
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(to = upDestinationSceneKey)
@@ -507,8 +506,8 @@
testScope.runTest {
setAuthMethod(AuthenticationMethodModel.Password)
startPhoneCall()
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(to = upDestinationSceneKey)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
index d3fa360..cd79ed1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
@@ -54,7 +54,7 @@
private val kosmos = Kosmos()
private val testScope = kosmos.testScope
private val sceneInteractor = kosmos.sceneInteractor
- private val deviceEntryInteractor = kosmos.deviceEntryInteractor
+ private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor }
private lateinit var shadeInteractor: ShadeInteractor
private lateinit var underTest: ShadeControllerSceneImpl
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
index 4fe45f6..8c9036a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.userRepository
import com.google.common.truth.Truth
@@ -562,17 +563,17 @@
}
@Test
- fun isSplitShade() =
+ fun shadeMode() =
testScope.runTest {
- val isSplitShade by collectLastValue(underTest.isSplitShade)
+ val shadeMode by collectLastValue(underTest.shadeMode)
- shadeRepository.setSplitShade(true)
- assertThat(isSplitShade).isTrue()
+ shadeRepository.setShadeMode(ShadeMode.Split)
+ assertThat(shadeMode).isEqualTo(ShadeMode.Split)
- shadeRepository.setSplitShade(false)
- assertThat(isSplitShade).isFalse()
+ shadeRepository.setShadeMode(ShadeMode.Single)
+ assertThat(shadeMode).isEqualTo(ShadeMode.Single)
- shadeRepository.setSplitShade(true)
- assertThat(isSplitShade).isTrue()
+ shadeRepository.setShadeMode(ShadeMode.Split)
+ assertThat(shadeMode).isEqualTo(ShadeMode.Split)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
index cbb84da..31dacdd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
@@ -24,6 +24,7 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
@@ -42,20 +43,20 @@
private val underTest = kosmos.shadeStartable
@Test
- fun hydrateSplitShade() =
+ fun hydrateShadeMode() =
testScope.runTest {
overrideResource(R.bool.config_use_split_notification_shade, false)
- val isSplitShade by collectLastValue(shadeInteractor.isSplitShade)
+ val shadeMode by collectLastValue(shadeInteractor.shadeMode)
underTest.start()
- assertThat(isSplitShade).isFalse()
+ assertThat(shadeMode).isEqualTo(ShadeMode.Single)
overrideResource(R.bool.config_use_split_notification_shade, true)
fakeConfigurationRepository.onAnyConfigurationChange()
- assertThat(isSplitShade).isTrue()
+ assertThat(shadeMode).isEqualTo(ShadeMode.Split)
overrideResource(R.bool.config_use_split_notification_shade, false)
fakeConfigurationRepository.onAnyConfigurationChange()
- assertThat(isSplitShade).isFalse()
+ assertThat(shadeMode).isEqualTo(ShadeMode.Single)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 2e68d12..1c54961 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -37,10 +37,12 @@
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.privacyChipInteractor
import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.domain.startable.shadeStartable
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
@@ -72,6 +74,7 @@
private val testScope = kosmos.testScope
private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor }
+ private val shadeRepository by lazy { kosmos.shadeRepository }
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
@@ -274,4 +277,19 @@
assertThat(destinationScenes?.get(Swipe(SwipeDirection.Down))?.toScene)
.isEqualTo(Scenes.QuickSettings)
}
+
+ @Test
+ fun shadeMode() =
+ testScope.runTest {
+ val shadeMode by collectLastValue(underTest.shadeMode)
+
+ shadeRepository.setShadeMode(ShadeMode.Split)
+ assertThat(shadeMode).isEqualTo(ShadeMode.Split)
+
+ shadeRepository.setShadeMode(ShadeMode.Single)
+ assertThat(shadeMode).isEqualTo(ShadeMode.Single)
+
+ shadeRepository.setShadeMode(ShadeMode.Split)
+ assertThat(shadeMode).isEqualTo(ShadeMode.Split)
+ }
}
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 deb1976..278bf9b 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
@@ -21,13 +21,10 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
-import com.android.systemui.communal.domain.interactor.communalInteractor
-import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -41,6 +38,7 @@
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
+import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel
import com.android.systemui.kosmos.testScope
@@ -80,13 +78,13 @@
init {
kosmos.aodBurnInViewModel = aodBurnInViewModel
}
+
val testScope = kosmos.testScope
val configurationRepository = kosmos.fakeConfigurationRepository
val keyguardRepository = kosmos.fakeKeyguardRepository
val keyguardInteractor = kosmos.keyguardInteractor
val keyguardRootViewModel = kosmos.keyguardRootViewModel
val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
- val communalInteractor = kosmos.communalInteractor
val shadeRepository = kosmos.shadeRepository
val sharedNotificationContainerInteractor = kosmos.sharedNotificationContainerInteractor
val largeScreenHeaderHelper = kosmos.mockLargeScreenHeaderHelper
@@ -239,7 +237,7 @@
}
@Test
- fun glanceableHubAlpha() =
+ fun glanceableHubAlpha_lockscreenToHub() =
testScope.runTest {
val alpha by collectLastValue(underTest.glanceableHubAlpha)
@@ -278,12 +276,6 @@
value = 1f,
)
)
- val idleTransitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(CommunalScenes.Communal)
- )
- communalInteractor.setTransitionState(idleTransitionState)
- runCurrent()
assertThat(alpha).isEqualTo(0f)
// While state is GLANCEABLE_HUB, verify alpha is restored to full if glanceable hub is
@@ -293,6 +285,50 @@
}
@Test
+ fun glanceableHubAlpha_dreamToHub() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.glanceableHubAlpha)
+
+ // Start on dream
+ showDream()
+ assertThat(alpha).isEqualTo(1f)
+
+ // Start transitioning to glanceable hub
+ val progress = 0.6f
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ value = 0f,
+ )
+ )
+ runCurrent()
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.RUNNING,
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ value = progress,
+ )
+ )
+ runCurrent()
+ // Keep notifications hidden during the transition from dream to hub
+ assertThat(alpha).isEqualTo(0)
+
+ // Finish transition to glanceable hub
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ value = 1f,
+ )
+ )
+ assertThat(alpha).isEqualTo(0f)
+ }
+
+ @Test
fun validateMarginTop() =
testScope.runTest {
overrideResource(R.bool.config_use_large_screen_shade_header, false)
@@ -391,12 +427,11 @@
assertThat(isOnGlanceableHubWithoutShade).isFalse()
// Move to glanceable hub
- val idleTransitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(CommunalScenes.Communal)
- )
- communalInteractor.setTransitionState(idleTransitionState)
- runCurrent()
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope = this
+ )
assertThat(isOnGlanceableHubWithoutShade).isTrue()
// While state is GLANCEABLE_HUB, validate variations of both shade and qs expansion
@@ -665,6 +700,25 @@
}
@Test
+ fun alphaOnFullQsExpansion() =
+ testScope.runTest {
+ val viewState = ViewStateAccessor()
+ val alpha by collectLastValue(underTest.keyguardAlpha(viewState))
+
+ showLockscreenWithQSExpanded()
+
+ // Alpha fades out as QS expands
+ shadeRepository.setQsExpansion(0.5f)
+ assertThat(alpha).isWithin(0.01f).of(0.5f)
+ shadeRepository.setQsExpansion(0.9f)
+ assertThat(alpha).isWithin(0.01f).of(0.1f)
+
+ // Ensure that alpha is set back to 1f when QS is fully expanded
+ shadeRepository.setQsExpansion(1f)
+ assertThat(alpha).isEqualTo(1f)
+ }
+
+ @Test
fun shadeCollapseFadeIn() =
testScope.runTest {
val fadeIn by collectLastValue(underTest.shadeCollapseFadeIn)
@@ -726,6 +780,19 @@
)
}
+ private suspend fun TestScope.showDream() {
+ shadeRepository.setLockscreenShadeExpansion(0f)
+ shadeRepository.setQsExpansion(0f)
+ runCurrent()
+ keyguardRepository.setDreaming(true)
+ runCurrent()
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ testScope,
+ )
+ }
+
private suspend fun TestScope.showLockscreenWithShadeExpanded() {
shadeRepository.setLockscreenShadeExpansion(1f)
shadeRepository.setQsExpansion(0f)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
new file mode 100644
index 0000000..183a58a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.policy
+
+import android.app.Notification
+import android.platform.test.annotations.EnableFlags
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.settings.FakeGlobalSettings
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.invocation.InvocationOnMock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@SmallTest
+@RunWithLooper
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(NotificationThrottleHun.FLAG_NAME)
+class AvalancheControllerTest : SysuiTestCase() {
+
+ private val mAvalancheController = AvalancheController()
+
+ // For creating mocks
+ @get:Rule var rule: MockitoRule = MockitoJUnit.rule()
+ @Mock private val runnableMock: Runnable? = null
+
+ // For creating TestableHeadsUpManager
+ @Mock private val mAccessibilityMgr: AccessibilityManagerWrapper? = null
+ private val mUiEventLoggerFake = UiEventLoggerFake()
+ private val mLogger = Mockito.spy(HeadsUpManagerLogger(logcatLogBuffer()))
+ private val mGlobalSettings = FakeGlobalSettings()
+ private val mSystemClock = FakeSystemClock()
+ private val mExecutor = FakeExecutor(mSystemClock)
+ private var testableHeadsUpManager: BaseHeadsUpManager? = null
+
+ @Before
+ fun setUp() {
+ // Use default non-a11y timeout
+ Mockito.`when`(
+ mAccessibilityMgr!!.getRecommendedTimeoutMillis(
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt()
+ )
+ )
+ .then { i: InvocationOnMock -> i.getArgument(0) }
+
+ // Initialize TestableHeadsUpManager here instead of at declaration, when mocks will be null
+ testableHeadsUpManager =
+ TestableHeadsUpManager(
+ mContext,
+ mLogger,
+ mExecutor,
+ mGlobalSettings,
+ mSystemClock,
+ mAccessibilityMgr,
+ mUiEventLoggerFake,
+ mAvalancheController
+ )
+ }
+
+ private fun createHeadsUpEntry(id: Int): BaseHeadsUpManager.HeadsUpEntry {
+ val entry = testableHeadsUpManager!!.createHeadsUpEntry()
+
+ entry.setEntry(
+ NotificationEntryBuilder()
+ .setSbn(HeadsUpManagerTestUtil.createSbn(id, Notification.Builder(mContext, "")))
+ .build()
+ )
+ return entry
+ }
+
+ @Test
+ fun testUpdate_isShowing_runsRunnable() {
+ // Entry is showing
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = headsUpEntry
+
+ // Update
+ mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Runnable was run
+ Mockito.verify(runnableMock, Mockito.times(1)).run()
+ }
+
+ @Test
+ fun testUpdate_noneShowingAndNotNext_showNow() {
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+
+ // None showing
+ mAvalancheController.headsUpEntryShowing = null
+
+ // Entry is NOT next
+ mAvalancheController.clearNext()
+
+ // Update
+ mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Entry is showing now
+ Truth.assertThat(mAvalancheController.headsUpEntryShowing).isEqualTo(headsUpEntry)
+ }
+
+ @Test
+ fun testUpdate_isNext_addsRunnable() {
+ // Another entry is already showing
+ val otherShowingEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = otherShowingEntry
+
+ // Entry is next
+ val headsUpEntry = createHeadsUpEntry(id = 1)
+ mAvalancheController.addToNext(headsUpEntry, runnableMock!!)
+
+ // Entry has one Runnable
+ val runnableList: List<Runnable?>? = mAvalancheController.nextMap[headsUpEntry]
+ Truth.assertThat(runnableList).isNotNull()
+ Truth.assertThat(runnableList!!.size).isEqualTo(1)
+
+ // Update
+ mAvalancheController.update(headsUpEntry, runnableMock, "testLabel")
+
+ // Entry has two Runnables
+ Truth.assertThat(runnableList.size).isEqualTo(2)
+ }
+
+ @Test
+ fun testUpdate_isNotNextWithOtherHunShowing_isNext() {
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+
+ // Another entry is already showing
+ val otherShowingEntry = createHeadsUpEntry(id = 1)
+ mAvalancheController.headsUpEntryShowing = otherShowingEntry
+
+ // Entry is NOT next
+ mAvalancheController.clearNext()
+
+ // Update
+ mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Entry is next
+ Truth.assertThat(mAvalancheController.nextMap.containsKey(headsUpEntry)).isTrue()
+ }
+
+ @Test
+ fun testDelete_isNext_removedFromNext_runnableNotRun() {
+ // Entry is next
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.addToNext(headsUpEntry, runnableMock!!)
+
+ // Delete
+ mAvalancheController.delete(headsUpEntry, runnableMock, "testLabel")
+
+ // Entry was removed from next
+ Truth.assertThat(mAvalancheController.nextMap.containsKey(headsUpEntry)).isFalse()
+
+ // Runnable was not run
+ Mockito.verify(runnableMock, Mockito.times(0)).run()
+ }
+
+ @Test
+ fun testDelete_wasDropped_removedFromDropSet() {
+ // Entry was dropped
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.debugDropSet.add(headsUpEntry)
+
+ // Delete
+ mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Entry was removed from dropSet
+ Truth.assertThat(mAvalancheController.debugDropSet.contains(headsUpEntry)).isFalse()
+ }
+
+ @Test
+ fun testDelete_wasDropped_runnableNotRun() {
+ // Entry was dropped
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.debugDropSet.add(headsUpEntry)
+
+ // Delete
+ mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Runnable was not run
+ Mockito.verify(runnableMock, Mockito.times(0)).run()
+ }
+
+ @Test
+ fun testDelete_isShowing_runnableRun() {
+ // Entry is showing
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = headsUpEntry
+
+ // Delete
+ mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Runnable was run
+ Mockito.verify(runnableMock, Mockito.times(1)).run()
+ }
+
+ @Test
+ fun testDelete_isShowing_showNext() {
+ // Entry is showing
+ val showingEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = showingEntry
+
+ // There's another entry waiting to show next
+ val nextEntry = createHeadsUpEntry(id = 1)
+ mAvalancheController.addToNext(nextEntry, runnableMock!!)
+
+ // Delete
+ mAvalancheController.delete(showingEntry, runnableMock, "testLabel")
+
+ // Next entry is shown
+ Truth.assertThat(mAvalancheController.headsUpEntryShowing).isEqualTo(nextEntry)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index db4d42f..830bcef 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -35,13 +35,10 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.ActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Person;
import android.content.Intent;
-import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
import android.testing.TestableLooper;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -77,6 +74,8 @@
private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
+ private AvalancheController mAvalancheController = new AvalancheController();
+
@Mock private AccessibilityManagerWrapper mAccessibilityMgr;
protected static final int TEST_MINIMUM_DISPLAY_TIME = 400;
@@ -99,7 +98,7 @@
private BaseHeadsUpManager createHeadsUpManager() {
return new TestableHeadsUpManager(mContext, mLogger, mExecutor, mGlobalSettings,
- mSystemClock, mAccessibilityMgr, mUiEventLoggerFake);
+ mSystemClock, mAccessibilityMgr, mUiEventLoggerFake, mAvalancheController);
}
private NotificationEntry createStickyEntry(int id) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
index c032d7c..61a79d8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
@@ -28,8 +28,8 @@
import android.content.Context;
import android.testing.TestableLooper;
-import androidx.test.filters.SmallTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -76,6 +76,7 @@
@Mock private UiEventLogger mUiEventLogger;
@Mock private JavaAdapter mJavaAdapter;
@Mock private ShadeInteractor mShadeInteractor;
+ private AvalancheController mAvalancheController = new AvalancheController();
private static final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone {
TestableHeadsUpManagerPhone(
@@ -92,7 +93,8 @@
AccessibilityManagerWrapper accessibilityManagerWrapper,
UiEventLogger uiEventLogger,
JavaAdapter javaAdapter,
- ShadeInteractor shadeInteractor
+ ShadeInteractor shadeInteractor,
+ AvalancheController avalancheController
) {
super(
context,
@@ -109,7 +111,8 @@
accessibilityManagerWrapper,
uiEventLogger,
javaAdapter,
- shadeInteractor
+ shadeInteractor,
+ avalancheController
);
mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
@@ -131,12 +134,15 @@
mAccessibilityManagerWrapper,
mUiEventLogger,
mJavaAdapter,
- mShadeInteractor
+ mShadeInteractor,
+ mAvalancheController
);
}
@Before
public void setUp() {
+ // TODO(b/315362456) create separate test with the flag disabled
+ // then modify this file to test with the flag enabled
mSetFlagsRule.disableFlags(NotificationThrottleHun.FLAG_NAME);
when(mShadeInteractor.isAnyExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false));
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
index 2747629..d8f77f0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
@@ -43,9 +43,10 @@
GlobalSettings globalSettings,
SystemClock systemClock,
AccessibilityManagerWrapper accessibilityManagerWrapper,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger,
+ AvalancheController avalancheController) {
super(context, logger, mockExecutorHandler(executor), globalSettings, systemClock,
- executor, accessibilityManagerWrapper, uiEventLogger);
+ executor, accessibilityManagerWrapper, uiEventLogger, avalancheController);
mTouchAcceptanceDelay = BaseHeadsUpManagerTest.TEST_TOUCH_ACCEPTANCE_TIME;
mMinimumDisplayTime = BaseHeadsUpManagerTest.TEST_MINIMUM_DISPLAY_TIME;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
index a2f3ccb..e06efe8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
@@ -113,6 +113,28 @@
}
@Test
+ fun zenMuted_cantChange() {
+ with(kosmos) {
+ testScope.runTest {
+ notificationsSoundPolicyRepository.updateNotificationPolicy()
+ notificationsSoundPolicyRepository.updateZenMode(
+ ZenMode(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS)
+ )
+
+ val canChangeVolume by
+ collectLastValue(
+ underTest.canChangeVolume(AudioStream(AudioManager.STREAM_NOTIFICATION))
+ )
+
+ underTest.setMuted(AudioStream(AudioManager.STREAM_RING), true)
+ runCurrent()
+
+ assertThat(canChangeVolume).isFalse()
+ }
+ }
+ }
+
+ @Test
fun streamIsMuted_getStream_volumeZero() {
with(kosmos) {
testScope.runTest {
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index f51e109..7341015 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -304,6 +304,15 @@
<!-- An explanation text that the password needs to be entered since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] -->
<string name="kg_prompt_reason_timeout_password">For additional security, use password instead</string>
+ <!-- An explanation text that the pin needs to be provided to enter the device for security reasons. [CHAR LIMIT=70] -->
+ <string name="kg_prompt_added_security_pin">PIN required for additional security</string>
+
+ <!-- An explanation text that the pattern needs to be provided to enter the device for security reasons. [CHAR LIMIT=70] -->
+ <string name="kg_prompt_added_security_pattern">Pattern required for additional security</string>
+
+ <!-- An explanation text that the password needs to be provided to enter the device for security reasons. [CHAR LIMIT=70] -->
+ <string name="kg_prompt_added_security_password">Password required for additional security</string>
+
<!-- An explanation text that the credential needs to be entered because a device admin has
locked the device. [CHAR LIMIT=80] -->
<string name="kg_prompt_reason_device_admin">Device locked by admin</string>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 517f88b..b8f20f6 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1135,6 +1135,14 @@
<string name="hub_mode_add_widget_button_text">Add widget</string>
<!-- Text for the button that exits the hub mode editing mode. [CHAR LIMIT=50] -->
<string name="hub_mode_editing_exit_button_text">Done</string>
+ <!-- Title for the dialog that redirects users to change allowed widget category in settings. [CHAR LIMIT=NONE] -->
+ <string name="dialog_title_to_allow_any_widget">Allow any widget on lock screen?</string>
+ <!-- Text for the button in the dialog that opens when tapping on disabled widgets. [CHAR LIMIT=NONE] -->
+ <string name="button_text_to_open_settings">Open settings</string>
+ <!-- Title of a dialog. This text is confirming that the user wants to turn on access to their work apps, which the user had previously paused. "Work" is an adjective. [CHAR LIMIT=30] -->
+ <string name="work_mode_off_title">Unpause work apps?</string>
+ <!-- Title for button to unpause on work profile. [CHAR LIMIT=NONE] -->
+ <string name="work_mode_turn_on">Unpause</string>
<!-- Related to user switcher --><skip/>
@@ -1528,8 +1536,12 @@
<!-- Media device casting volume slider label [CHAR_LIMIT=20] -->
<string name="media_device_cast">Cast</string>
- <!-- A message shown when the notification volume changing is disabled because of the muted ring stream [CHAR_LIMIT=40]-->
+ <!-- A message shown when the notification volume changing is disabled because of the muted ring stream [CHAR_LIMIT=50]-->
<string name="stream_notification_unavailable">Unavailable because ring is muted</string>
+ <!-- A message shown when the alarm volume changing is disabled because of the don't disturb mode [CHAR_LIMIT=50]-->
+ <string name="stream_alarm_unavailable">Unavailable because Do Not Disturb is on</string>
+ <!-- A message shown when the media volume changing is disabled because of the don't disturb mode [CHAR_LIMIT=50]-->
+ <string name="stream_media_unavailable">Unavailable because Do Not Disturb is on</string>
<!-- Shown in the header of quick settings to indicate to the user that their phone ringer is on vibrate. [CHAR_LIMIT=NONE] -->
<!-- Shown in the header of quick settings to indicate to the user that their phone ringer is on silent (muted). [CHAR_LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 4e7809a..59516be 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -965,6 +965,10 @@
<item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
</style>
+ <style name="Widget.SliceView.VolumePanel">
+ <item name="hideHeaderRow">true</item>
+ </style>
+
<style name="Theme.VolumePanelActivity.Popup" parent="@style/Theme.SystemUI.Dialog">
<item name="android:dialogCornerRadius">44dp</item>
<item name="android:colorBackground">?androidprv:attr/materialColorSurfaceContainerHigh
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 6e611fe..42ba05c 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -35,6 +35,9 @@
srcs: [
":statslog-SystemUI-java-gen",
],
+ libs: [
+ "androidx.annotation_annotation",
+ ],
}
android_library {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 84c8ea7..26e91b6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -122,7 +122,7 @@
case PROMPT_REASON_USER_REQUEST:
return R.string.kg_prompt_after_user_lockdown_password;
case PROMPT_REASON_PREPARE_FOR_UPDATE:
- return R.string.kg_prompt_reason_timeout_password;
+ return R.string.kg_prompt_added_security_password;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
return R.string.kg_prompt_reason_timeout_password;
case PROMPT_REASON_TRUSTAGENT_EXPIRED:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index bf8900d..caa74780 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -323,7 +323,7 @@
resId = R.string.kg_prompt_after_user_lockdown_pattern;
break;
case PROMPT_REASON_PREPARE_FOR_UPDATE:
- resId = R.string.kg_prompt_reason_timeout_pattern;
+ resId = R.string.kg_prompt_added_security_pattern;
break;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
resId = R.string.kg_prompt_reason_timeout_pattern;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index bcab6f0..fbe9edf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -134,7 +134,7 @@
case PROMPT_REASON_USER_REQUEST:
return R.string.kg_prompt_after_user_lockdown_pin;
case PROMPT_REASON_PREPARE_FOR_UPDATE:
- return R.string.kg_prompt_reason_timeout_pin;
+ return R.string.kg_prompt_added_security_pin;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
return R.string.kg_prompt_reason_timeout_pin;
case PROMPT_REASON_TRUSTAGENT_EXPIRED:
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
index aab0b1e..e88aaf01 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
@@ -22,6 +22,7 @@
import android.content.ContentProvider
import android.content.Context
import android.content.Intent
+import android.util.Log
import androidx.core.app.AppComponentFactory
import com.android.systemui.dagger.ContextComponentHelper
import com.android.systemui.dagger.SysUIComponent
@@ -90,8 +91,7 @@
return app
}
- @UsesReflection(
- KeepTarget(instanceOfClassConstant = SysUIComponent::class, methodName = "inject"))
+ @UsesReflection(KeepTarget(instanceOfClassConstant = SysUIComponent::class, methodName = "inject"))
override fun instantiateProviderCompat(cl: ClassLoader, className: String): ContentProvider {
val contentProvider = super.instantiateProviderCompat(cl, className)
if (contentProvider is ContextInitializer) {
@@ -103,12 +103,11 @@
.getMethod("inject", contentProvider.javaClass)
injectMethod.invoke(rootComponent, contentProvider)
} catch (e: NoSuchMethodException) {
- throw RuntimeException("No injector for class: " + contentProvider.javaClass, e)
+ Log.w(TAG, "No injector for class: " + contentProvider.javaClass, e)
} catch (e: IllegalAccessException) {
- throw RuntimeException("Injector inaccessible for class: " +
- contentProvider.javaClass, e)
+ Log.w(TAG, "No injector for class: " + contentProvider.javaClass, e)
} catch (e: InvocationTargetException) {
- throw RuntimeException("Error while injecting: " + contentProvider.javaClass, e)
+ Log.w(TAG, "No injector for class: " + contentProvider.javaClass, e)
}
initializer
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
index 0ef3d20..a90d4b2 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
@@ -118,6 +118,12 @@
iconResId = R.drawable.pip_ic_close_white
)
)
+
+ // Ensure this is unfocusable & uninteractable
+ isClickable = false
+ isFocusable = false
+ importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
+
// END DragToInteractView modification
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index d3e85e0..1f04599 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -113,13 +113,8 @@
/* Moves position without updating underlying percentage position. Can be animated. */
void moveToPosition(PointF position, boolean animateMovement) {
- if (Flags.floatingMenuImeDisplacementAnimation()) {
- moveToPositionX(position.x, animateMovement);
- moveToPositionY(position.y, animateMovement);
- } else {
- moveToPositionX(position.x, /* animateMovement = */ false);
- moveToPositionY(position.y, /* animateMovement = */ false);
- }
+ moveToPositionX(position.x, animateMovement);
+ moveToPositionY(position.y, animateMovement);
}
void moveToPositionX(float positionX) {
@@ -127,7 +122,7 @@
}
void moveToPositionX(float positionX, boolean animateMovement) {
- if (animateMovement && Flags.floatingMenuImeDisplacementAnimation()) {
+ if (animateMovement) {
springMenuWith(DynamicAnimation.TRANSLATION_X,
createSpringForce(),
/* velocity = */ 0,
@@ -142,7 +137,7 @@
}
void moveToPositionY(float positionY, boolean animateMovement) {
- if (animateMovement && Flags.floatingMenuImeDisplacementAnimation()) {
+ if (animateMovement) {
springMenuWith(DynamicAnimation.TRANSLATION_Y,
createSpringForce(),
/* velocity = */ 0,
@@ -455,7 +450,7 @@
? MIN_PERCENT
: Math.min(MAX_PERCENT, position.y / draggableBounds.height());
- if (Flags.floatingMenuImeDisplacementAnimation() && !writeToPosition) {
+ if (!writeToPosition) {
mMenuView.onEdgeChangedIfNeeded();
} else {
mMenuView.persistPositionAndUpdateEdge(new Position(percentageX, percentageY));
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
index e57323b..35fe6b1 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
@@ -74,6 +74,12 @@
addView(mTextView, Index.TEXT_VIEW,
new LayoutParams(/* width= */ 0, WRAP_CONTENT, /* weight= */ 1));
addView(mUndoButton, Index.UNDO_BUTTON, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+
+ // The message box is not focusable, but will announce its contents when it appears.
+ // The textView and button are still interactable.
+ setClickable(false);
+ setFocusable(false);
+ setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
index 577bbc0..270fedc 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -101,6 +101,10 @@
loadLayoutResources();
addView(mTargetFeaturesView);
+
+ setClickable(false);
+ setFocusable(false);
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
}
@Override
@@ -224,8 +228,7 @@
}
// We can skip animating if FAB is not visible
- if (Flags.floatingMenuImeDisplacementAnimation()
- && animateMovement && getVisibility() == VISIBLE) {
+ if (animateMovement && getVisibility() == VISIBLE) {
mMenuAnimationController.moveToPosition(position, /* animateMovement = */ true);
// onArrivalAtPosition() is called at the end of the animation.
} else {
@@ -331,7 +334,7 @@
mMoveToTuckedListener.onMoveToTuckedChanged(isMoveToTucked);
}
- if (Flags.floatingMenuOverlapsNavBarsFlag() && !Flags.floatingMenuAnimatedTuck()) {
+ if (!Flags.floatingMenuAnimatedTuck()) {
if (isMoveToTucked) {
final float halfWidth = getMenuWidth() / 2.0f;
final boolean isOnLeftSide = mMenuAnimationController.isOnLeftSide();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
index 4865fce..760e1c3 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
@@ -34,7 +34,6 @@
import androidx.annotation.DimenRes;
-import com.android.systemui.Flags;
import com.android.systemui.res.R;
import java.lang.annotation.Retention;
@@ -155,11 +154,6 @@
final int margin = getMenuMargin();
final Rect draggableBounds = new Rect(getWindowAvailableBounds());
- if (!Flags.floatingMenuOverlapsNavBarsFlag()) {
- // Initializes start position for mapping the translation of the menu view.
- draggableBounds.offsetTo(/* newLeft= */ 0, /* newTop= */ 0);
- }
-
draggableBounds.top += margin;
draggableBounds.right -= getMenuWidth();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index cd3b8a6..97e38f4 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -319,6 +319,9 @@
if (Flags.floatingMenuAnimatedTuck()) {
setClipChildren(true);
}
+ setClickable(false);
+ setFocusable(false);
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
}
@Override
@@ -443,21 +446,18 @@
}
public void onMoveToTuckedChanged(boolean moveToTuck) {
- if (Flags.floatingMenuOverlapsNavBarsFlag()) {
- if (moveToTuck) {
- final Rect bounds = mMenuViewAppearance.getWindowAvailableBounds();
- final int[] location = getLocationOnScreen();
- bounds.offset(
- location[0],
- location[1]
- );
+ if (moveToTuck) {
+ final Rect bounds = mMenuViewAppearance.getWindowAvailableBounds();
+ final int[] location = getLocationOnScreen();
+ bounds.offset(
+ location[0],
+ location[1]
+ );
- setClipBounds(bounds);
- }
- // Instead of clearing clip bounds when moveToTuck is false,
- // wait until the spring animation finishes.
+ setClipBounds(bounds);
}
- // Function is a no-operation if flag is disabled.
+ // Instead of clearing clip bounds when moveToTuck is false,
+ // wait until the spring animation finishes.
}
private void onSpringAnimationsEndAction() {
@@ -475,9 +475,7 @@
setClipBounds(null);
}
}
- if (Flags.floatingMenuImeDisplacementAnimation()) {
- mMenuView.onArrivalAtPosition(false);
- }
+ mMenuView.onArrivalAtPosition(false);
}
void dispatchAccessibilityAction(int id) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
index bc9d1ff..6b1240b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
@@ -20,11 +20,9 @@
import android.content.Context;
import android.graphics.PixelFormat;
-import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
-import com.android.systemui.Flags;
import com.android.systemui.util.settings.SecureSettings;
/**
@@ -88,14 +86,9 @@
params.privateFlags |= PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
params.windowAnimations = android.R.style.Animation_Translucent;
// Insets are configured to allow the menu to display over navigation and system bars.
- if (Flags.floatingMenuOverlapsNavBarsFlag()) {
- params.setFitInsetsTypes(0);
- params.layoutInDisplayCutoutMode =
- WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- } else {
- params.setFitInsetsTypes(
- WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
- }
+ params.setFitInsetsTypes(0);
+ params.layoutInDisplayCutoutMode =
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
return params;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 9de71c1..8bd675c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -881,7 +881,7 @@
final Runnable endActionRunnable = () -> {
setVisibility(View.INVISIBLE);
- if (Flags.customBiometricPrompt()) {
+ if (Flags.customBiometricPrompt() && constraintBp()) {
mPromptSelectorInteractorProvider.get().resetPrompt();
}
removeWindowIfAttached();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
index b87fadf..4d88f49 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
@@ -18,6 +18,7 @@
import android.hardware.biometrics.Flags
import android.hardware.biometrics.PromptInfo
+import com.android.systemui.Flags.constraintBp
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.Utils.isDeviceCredentialAllowed
@@ -151,6 +152,7 @@
val hasCredentialViewShown = kind.value !is PromptKind.Biometric
val showBpForCredential =
Flags.customBiometricPrompt() &&
+ constraintBp() &&
!Utils.isBiometricAllowed(promptInfo) &&
isDeviceCredentialAllowed(promptInfo) &&
promptInfo.contentView != null
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index e48f05d..7bb75bf 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -22,6 +22,7 @@
import android.hardware.biometrics.BiometricAuthenticator
import android.hardware.biometrics.BiometricConstants
import android.hardware.biometrics.BiometricPrompt
+import android.hardware.biometrics.Flags
import android.hardware.face.FaceManager
import android.text.method.ScrollingMovementMethod
import android.util.Log
@@ -166,11 +167,14 @@
titleView.text = viewModel.title.first()
subtitleView.text = viewModel.subtitle.first()
descriptionView.text = viewModel.description.first()
- BiometricCustomizedViewBinder.bind(
- customizedViewContainer,
- view.requireViewById(R.id.space_above_content),
- viewModel
- )
+
+ if (Flags.customBiometricPrompt() && constraintBp()) {
+ BiometricCustomizedViewBinder.bind(
+ customizedViewContainer,
+ view.requireViewById(R.id.space_above_content),
+ viewModel
+ )
+ }
// set button listeners
negativeButton.setOnClickListener { legacyCallback.onButtonNegative() }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
index 3469cfa..e457601 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
@@ -86,7 +86,12 @@
launch {
var width = 0
var height = 0
- viewModel.activeAuthType.collect { activeAuthType ->
+ combine(promptViewModel.size, viewModel.activeAuthType, ::Pair).collect {
+ (_, activeAuthType) ->
+ // Every time after bp shows, [isIconViewLoaded] is set to false in
+ // [BiometricViewSizeBinder]. Then when biometric prompt view is redrew
+ // (when size or activeAuthType changes), we need to update
+ // [isIconViewLoaded] here to keep it correct.
when (activeAuthType) {
AuthType.Fingerprint,
AuthType.Coex -> {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
index 9c28f1c..9949e4c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -141,6 +141,9 @@
/** Hide the side fingerprint sensor indicator */
private fun hide() {
if (overlayView != null) {
+ val lottie = overlayView!!.requireViewById<LottieAnimationView>(R.id.sidefps_animation)
+ lottie.pauseAnimation()
+ lottie.removeAllLottieOnCompositionLoadedListener()
windowManager.get().removeView(overlayView)
overlayView = null
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 61aeffe..86b0b44 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -29,6 +29,7 @@
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import com.android.systemui.Flags.bpTalkback
+import com.android.systemui.Flags.constraintBp
import com.android.systemui.biometrics.UdfpsUtils
import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
@@ -217,18 +218,12 @@
*/
val faceMode: Flow<Boolean> =
combine(modalities, isConfirmationRequired, fingerprintStartMode) {
- modalities: BiometricModalities,
- isConfirmationRequired: Boolean,
- fingerprintStartMode: FingerprintStartMode ->
- if (modalities.hasFaceAndFingerprint) {
- if (isConfirmationRequired) {
- false
- } else {
- !fingerprintStartMode.isStarted
- }
- } else {
- false
- }
+ modalities,
+ isConfirmationRequired,
+ fingerprintStartMode ->
+ modalities.hasFaceAndFingerprint &&
+ !isConfirmationRequired &&
+ fingerprintStartMode == FingerprintStartMode.Pending
}
.distinctUntilChanged()
@@ -248,14 +243,11 @@
* asset to be loaded before determining the prompt size.
*/
val isIconViewLoaded: Flow<Boolean> =
- combine(credentialKind, _isIconViewLoaded.asStateFlow()) { credentialKind, isIconViewLoaded
- ->
- if (credentialKind is PromptKind.Biometric) {
- isIconViewLoaded
- } else {
- true
+ combine(modalities, _isIconViewLoaded.asStateFlow()) { modalities, isIconViewLoaded ->
+ val noIcon = modalities.isEmpty
+ noIcon || isIconViewLoaded
}
- }
+ .distinctUntilChanged()
// Sets whether the prompt's iconView animation has been loaded in the view yet.
fun setIsIconViewLoaded(iconViewLoaded: Boolean) {
@@ -284,7 +276,7 @@
promptSelectorInteractor.prompt
.map {
when {
- !customBiometricPrompt() || it == null -> null
+ !(customBiometricPrompt() && constraintBp()) || it == null -> null
it.logoRes != -1 -> context.resources.getDrawable(it.logoRes, context.theme)
it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap)
else ->
@@ -304,7 +296,7 @@
promptSelectorInteractor.prompt
.map {
when {
- !customBiometricPrompt() || it == null -> ""
+ !(customBiometricPrompt() && constraintBp()) || it == null -> ""
it.logoDescription != null -> it.logoDescription
else ->
try {
@@ -329,7 +321,7 @@
/** Custom content view for the prompt. */
val contentView: Flow<PromptContentView?> =
promptSelectorInteractor.prompt
- .map { if (customBiometricPrompt()) it?.contentView else null }
+ .map { if (customBiometricPrompt() && constraintBp()) it?.contentView else null }
.distinctUntilChanged()
private val originalDescription =
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
index c25e748..7f6fc91 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
@@ -23,10 +23,12 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.Flags
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.biometrics.data.repository.FacePropertyRepository
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.bouncer.data.repository.BouncerMessageRepository
import com.android.systemui.bouncer.shared.model.BouncerMessageModel
+import com.android.systemui.bouncer.shared.model.BouncerMessageStrings
import com.android.systemui.bouncer.shared.model.Message
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -35,46 +37,6 @@
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.res.R.string.bouncer_face_not_recognized
-import com.android.systemui.res.R.string.keyguard_enter_password
-import com.android.systemui.res.R.string.keyguard_enter_pattern
-import com.android.systemui.res.R.string.keyguard_enter_pin
-import com.android.systemui.res.R.string.kg_bio_too_many_attempts_password
-import com.android.systemui.res.R.string.kg_bio_too_many_attempts_pattern
-import com.android.systemui.res.R.string.kg_bio_too_many_attempts_pin
-import com.android.systemui.res.R.string.kg_bio_try_again_or_password
-import com.android.systemui.res.R.string.kg_bio_try_again_or_pattern
-import com.android.systemui.res.R.string.kg_bio_try_again_or_pin
-import com.android.systemui.res.R.string.kg_face_locked_out
-import com.android.systemui.res.R.string.kg_fp_not_recognized
-import com.android.systemui.res.R.string.kg_primary_auth_locked_out_password
-import com.android.systemui.res.R.string.kg_primary_auth_locked_out_pattern
-import com.android.systemui.res.R.string.kg_primary_auth_locked_out_pin
-import com.android.systemui.res.R.string.kg_prompt_after_adaptive_auth_lock
-import com.android.systemui.res.R.string.kg_prompt_after_dpm_lock
-import com.android.systemui.res.R.string.kg_prompt_after_update_password
-import com.android.systemui.res.R.string.kg_prompt_after_update_pattern
-import com.android.systemui.res.R.string.kg_prompt_after_update_pin
-import com.android.systemui.res.R.string.kg_prompt_after_user_lockdown_password
-import com.android.systemui.res.R.string.kg_prompt_after_user_lockdown_pattern
-import com.android.systemui.res.R.string.kg_prompt_after_user_lockdown_pin
-import com.android.systemui.res.R.string.kg_prompt_auth_timeout
-import com.android.systemui.res.R.string.kg_prompt_password_auth_timeout
-import com.android.systemui.res.R.string.kg_prompt_pattern_auth_timeout
-import com.android.systemui.res.R.string.kg_prompt_pin_auth_timeout
-import com.android.systemui.res.R.string.kg_prompt_reason_restart_password
-import com.android.systemui.res.R.string.kg_prompt_reason_restart_pattern
-import com.android.systemui.res.R.string.kg_prompt_reason_restart_pin
-import com.android.systemui.res.R.string.kg_prompt_unattended_update
-import com.android.systemui.res.R.string.kg_too_many_failed_attempts_countdown
-import com.android.systemui.res.R.string.kg_trust_agent_disabled
-import com.android.systemui.res.R.string.kg_unlock_with_password_or_fp
-import com.android.systemui.res.R.string.kg_unlock_with_pattern_or_fp
-import com.android.systemui.res.R.string.kg_unlock_with_pin_or_fp
-import com.android.systemui.res.R.string.kg_wrong_input_try_fp_suggestion
-import com.android.systemui.res.R.string.kg_wrong_password_try_again
-import com.android.systemui.res.R.string.kg_wrong_pattern_try_again
-import com.android.systemui.res.R.string.kg_wrong_pin_try_again
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.Quint
import javax.inject.Inject
@@ -130,17 +92,22 @@
repository.setMessage(
when (biometricSourceType) {
BiometricSourceType.FINGERPRINT ->
- incorrectFingerprintInput(currentSecurityMode)
+ BouncerMessageStrings.incorrectFingerprintInput(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
BiometricSourceType.FACE ->
- incorrectFaceInput(
- currentSecurityMode,
- isFingerprintAuthCurrentlyAllowed.value
- )
+ BouncerMessageStrings.incorrectFaceInput(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
else ->
- defaultMessage(
- currentSecurityMode,
- isFingerprintAuthCurrentlyAllowed.value
- )
+ BouncerMessageStrings.defaultMessage(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
}
)
}
@@ -189,45 +156,79 @@
trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterReboot
) {
if (wasRebootedForMainlineUpdate) {
- authRequiredForMainlineUpdate(currentSecurityMode)
+ BouncerMessageStrings.authRequiredForMainlineUpdate(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
} else {
- authRequiredAfterReboot(currentSecurityMode)
+ BouncerMessageStrings.authRequiredAfterReboot(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
}
} else if (trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterTimeout) {
- authRequiredAfterPrimaryAuthTimeout(currentSecurityMode)
+ BouncerMessageStrings.authRequiredAfterPrimaryAuthTimeout(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
} else if (flags.isPrimaryAuthRequiredAfterDpmLockdown) {
- authRequiredAfterAdminLockdown(currentSecurityMode)
+ BouncerMessageStrings.authRequiredAfterAdminLockdown(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
} else if (
- trustOrBiometricsAvailable && flags.primaryAuthRequiredForUnattendedUpdate
+ trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredForUnattendedUpdate
) {
- authRequiredForUnattendedUpdate(currentSecurityMode)
+ BouncerMessageStrings.authRequiredForUnattendedUpdate(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
} else if (fpLockedOut) {
- class3AuthLockedOut(currentSecurityMode)
+ BouncerMessageStrings.class3AuthLockedOut(currentSecurityMode.toAuthModel())
+ .toMessage()
} else if (faceLockedOut) {
if (isFaceAuthClass3) {
- class3AuthLockedOut(currentSecurityMode)
+ BouncerMessageStrings.class3AuthLockedOut(currentSecurityMode.toAuthModel())
+ .toMessage()
} else {
- faceLockedOut(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+ BouncerMessageStrings.faceLockedOut(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
}
} else if (flags.isSomeAuthRequiredAfterAdaptiveAuthRequest) {
- authRequiredAfterAdaptiveAuthRequest(
- currentSecurityMode,
- isFingerprintAuthCurrentlyAllowed.value
- )
+ BouncerMessageStrings.authRequiredAfterAdaptiveAuthRequest(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
} else if (
trustOrBiometricsAvailable &&
flags.strongerAuthRequiredAfterNonStrongBiometricsTimeout
) {
- nonStrongAuthTimeout(
- currentSecurityMode,
- isFingerprintAuthCurrentlyAllowed.value
- )
+ BouncerMessageStrings.nonStrongAuthTimeout(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
} else if (isTrustUsuallyManaged && flags.someAuthRequiredAfterUserRequest) {
- trustAgentDisabled(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+ BouncerMessageStrings.trustAgentDisabled(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
} else if (isTrustUsuallyManaged && flags.someAuthRequiredAfterTrustAgentExpired) {
- trustAgentDisabled(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+ BouncerMessageStrings.trustAgentDisabled(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
} else if (trustOrBiometricsAvailable && flags.isInUserLockdown) {
- authRequiredAfterUserLockdown(currentSecurityMode)
+ BouncerMessageStrings.authRequiredAfterUserLockdown(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
} else {
defaultMessage
}
@@ -244,7 +245,11 @@
override fun onTick(millisUntilFinished: Long) {
val secondsRemaining = (millisUntilFinished / 1000.0).roundToInt()
- val message = primaryAuthLockedOut(currentSecurityMode)
+ val message =
+ BouncerMessageStrings.primaryAuthLockedOut(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
message.message?.animate = false
message.message?.formatterArgs =
mutableMapOf<String, Any>(Pair("count", secondsRemaining))
@@ -258,7 +263,11 @@
if (!Flags.revampedBouncerMessages()) return
repository.setMessage(
- incorrectSecurityInput(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+ BouncerMessageStrings.incorrectSecurityInput(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
)
}
@@ -285,7 +294,12 @@
}
private val defaultMessage: BouncerMessageModel
- get() = defaultMessage(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+ get() =
+ BouncerMessageStrings.defaultMessage(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
fun onPrimaryBouncerUserInput() {
if (!Flags.revampedBouncerMessages()) return
@@ -354,283 +368,35 @@
return BouncerMessageModel(
message =
Message(
- messageResId = defaultMessage(securityMode, fpAuthIsAllowed).message?.messageResId,
+ messageResId =
+ BouncerMessageStrings.defaultMessage(
+ securityMode.toAuthModel(),
+ fpAuthIsAllowed
+ )
+ .toMessage()
+ .message
+ ?.messageResId,
animate = false
),
secondaryMessage = Message(message = secondaryMessage, animate = false)
)
}
-private fun defaultMessage(
- securityMode: SecurityMode,
- fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
- return if (fpAuthIsAllowed) {
- defaultMessageWithFingerprint(securityMode)
- } else
- when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
- SecurityMode.Password -> Pair(keyguard_enter_password, 0)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun defaultMessageWithFingerprint(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, 0)
- SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, 0)
- SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, 0)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun incorrectSecurityInput(
- securityMode: SecurityMode,
- fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
- return if (fpAuthIsAllowed) {
- incorrectSecurityInputWithFingerprint(securityMode)
- } else
- when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, 0)
- SecurityMode.Password -> Pair(kg_wrong_password_try_again, 0)
- SecurityMode.PIN -> Pair(kg_wrong_pin_try_again, 0)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun incorrectSecurityInputWithFingerprint(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, kg_wrong_input_try_fp_suggestion)
- SecurityMode.Password -> Pair(kg_wrong_password_try_again, kg_wrong_input_try_fp_suggestion)
- SecurityMode.PIN -> Pair(kg_wrong_pin_try_again, kg_wrong_input_try_fp_suggestion)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun incorrectFingerprintInput(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_pattern)
- SecurityMode.Password -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_password)
- SecurityMode.PIN -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun incorrectFaceInput(
- securityMode: SecurityMode,
- fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
- return if (fpAuthIsAllowed) incorrectFaceInputWithFingerprintAllowed(securityMode)
- else
- when (securityMode) {
- SecurityMode.Pattern -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_pattern)
- SecurityMode.Password -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_password)
- SecurityMode.PIN -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun incorrectFaceInputWithFingerprintAllowed(
- securityMode: SecurityMode
-): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, bouncer_face_not_recognized)
- SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, bouncer_face_not_recognized)
- SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, bouncer_face_not_recognized)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun biometricLockout(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_bio_too_many_attempts_pattern)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_bio_too_many_attempts_password)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_bio_too_many_attempts_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredAfterReboot(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_reason_restart_pattern)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_reason_restart_password)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredAfterAdminLockdown(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_dpm_lock)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_after_dpm_lock)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredAfterAdaptiveAuthRequest(
- securityMode: SecurityMode,
- fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
- return if (fpAuthIsAllowed) authRequiredAfterAdaptiveAuthRequestFingerprintAllowed(securityMode)
- else
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_adaptive_auth_lock)
- SecurityMode.Password ->
- Pair(keyguard_enter_password, kg_prompt_after_adaptive_auth_lock)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_adaptive_auth_lock)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredAfterAdaptiveAuthRequestFingerprintAllowed(
- securityMode: SecurityMode
-): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern ->
- Pair(kg_unlock_with_pattern_or_fp, kg_prompt_after_adaptive_auth_lock)
- SecurityMode.Password ->
- Pair(kg_unlock_with_password_or_fp, kg_prompt_after_adaptive_auth_lock)
- SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_prompt_after_adaptive_auth_lock)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredAfterUserLockdown(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_user_lockdown_pattern)
- SecurityMode.Password ->
- Pair(keyguard_enter_password, kg_prompt_after_user_lockdown_password)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredForUnattendedUpdate(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_unattended_update)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_unattended_update)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_unattended_update)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredForMainlineUpdate(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_update_pattern)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_after_update_password)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_update_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredAfterPrimaryAuthTimeout(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_pattern_auth_timeout)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_password_auth_timeout)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun nonStrongAuthTimeout(
- securityMode: SecurityMode,
- fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
- return if (fpAuthIsAllowed) {
- nonStrongAuthTimeoutWithFingerprintAllowed(securityMode)
- } else
- when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_auth_timeout)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_auth_timeout)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_auth_timeout)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-fun nonStrongAuthTimeoutWithFingerprintAllowed(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_prompt_auth_timeout)
- SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_prompt_auth_timeout)
- SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_prompt_auth_timeout)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun faceLockedOut(
- securityMode: SecurityMode,
- fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
- return if (fpAuthIsAllowed) faceLockedOutButFingerprintAvailable(securityMode)
- else
- when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_face_locked_out)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_face_locked_out)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_face_locked_out)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun faceLockedOutButFingerprintAvailable(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_face_locked_out)
- SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_face_locked_out)
- SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_face_locked_out)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun class3AuthLockedOut(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_bio_too_many_attempts_pattern)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_bio_too_many_attempts_password)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_bio_too_many_attempts_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun trustAgentDisabled(
- securityMode: SecurityMode,
- fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
- return if (fpAuthIsAllowed) trustAgentDisabledWithFingerprintAllowed(securityMode)
- else
- when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_trust_agent_disabled)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_trust_agent_disabled)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_trust_agent_disabled)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun trustAgentDisabledWithFingerprintAllowed(
- securityMode: SecurityMode
-): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_trust_agent_disabled)
- SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_trust_agent_disabled)
- SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_trust_agent_disabled)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun primaryAuthLockedOut(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern ->
- Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_pattern)
- SecurityMode.Password ->
- Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_password)
- SecurityMode.PIN ->
- Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
private fun Pair<Int, Int>.toMessage(): BouncerMessageModel {
return BouncerMessageModel(
message = Message(messageResId = this.first, animate = false),
secondaryMessage = Message(messageResId = this.second, animate = false)
)
}
+
+private fun SecurityMode.toAuthModel(): AuthenticationMethodModel {
+ return when (this) {
+ SecurityMode.Invalid -> AuthenticationMethodModel.None
+ SecurityMode.None -> AuthenticationMethodModel.None
+ SecurityMode.Pattern -> AuthenticationMethodModel.Pattern
+ SecurityMode.Password -> AuthenticationMethodModel.Password
+ SecurityMode.PIN -> AuthenticationMethodModel.Pin
+ SecurityMode.SimPin -> AuthenticationMethodModel.Sim
+ SecurityMode.SimPuk -> AuthenticationMethodModel.Sim
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
index c3d4cb3..7d3075a 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
@@ -44,6 +44,8 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -70,6 +72,9 @@
val isLockedEsim: StateFlow<Boolean?> = repository.isLockedEsim
val errorDialogMessage: StateFlow<String?> = repository.errorDialogMessage
+ private val _bouncerMessageChanged = MutableSharedFlow<String?>()
+ val bouncerMessageChanged: SharedFlow<String?> = _bouncerMessageChanged
+
/** Returns the default message for the sim pin screen. */
fun getDefaultMessage(): String {
val isEsimLocked = repository.isLockedEsim.value ?: false
@@ -81,7 +86,7 @@
return ""
}
- var count = telephonyManager.activeModemCount
+ val count = telephonyManager.activeModemCount
val info: SubscriptionInfo? = repository.activeSubscriptionInfo.value
val displayName = info?.displayName
var msg: String =
@@ -156,32 +161,24 @@
repository.setSimVerificationErrorMessage(null)
}
- /**
- * Based on sim state, unlock the locked sim with the given credentials.
- *
- * @return Any message that should show associated with the provided input. Null means that no
- * message needs to be shown.
- */
- suspend fun verifySim(input: List<Any>): String? {
+ /** Based on sim state, unlock the locked sim with the given credentials. */
+ suspend fun verifySim(input: List<Any>) {
+ val code = input.joinToString(separator = "")
if (repository.isSimPukLocked.value) {
- return verifySimPuk(input.joinToString(separator = ""))
+ verifySimPuk(code)
+ } else {
+ verifySimPin(code)
}
-
- return verifySimPin(input.joinToString(separator = ""))
}
- /**
- * Verifies the input and unlocks the locked sim with a 4-8 digit pin code.
- *
- * @return Any message that should show associated with the provided input. Null means that no
- * message needs to be shown.
- */
- private suspend fun verifySimPin(input: String): String? {
+ /** Verifies the input and unlocks the locked sim with a 4-8 digit pin code. */
+ private suspend fun verifySimPin(input: String) {
val subscriptionId = repository.subscriptionId.value
// A SIM PIN is 4 to 8 decimal digits according to
// GSM 02.17 version 5.0.1, Section 5.6 PIN Management
if (input.length < MIN_SIM_PIN_LENGTH || input.length > MAX_SIM_PIN_LENGTH) {
- return resources.getString(R.string.kg_invalid_sim_pin_hint)
+ _bouncerMessageChanged.emit(resources.getString(R.string.kg_invalid_sim_pin_hint))
+ return
}
val result =
withContext(backgroundDispatcher) {
@@ -190,8 +187,10 @@
telephonyManager.supplyIccLockPin(input)
}
when (result.result) {
- PinResult.PIN_RESULT_TYPE_SUCCESS ->
+ PinResult.PIN_RESULT_TYPE_SUCCESS -> {
keyguardUpdateMonitor.reportSimUnlocked(subscriptionId)
+ _bouncerMessageChanged.emit(null)
+ }
PinResult.PIN_RESULT_TYPE_INCORRECT -> {
if (result.attemptsRemaining <= CRITICAL_NUM_OF_ATTEMPTS) {
// Show a dialog to display the remaining number of attempts to verify the sim
@@ -199,24 +198,22 @@
repository.setSimVerificationErrorMessage(
getPinPasswordErrorMessage(result.attemptsRemaining)
)
+ _bouncerMessageChanged.emit(null)
} else {
- return getPinPasswordErrorMessage(result.attemptsRemaining)
+ _bouncerMessageChanged.emit(
+ getPinPasswordErrorMessage(result.attemptsRemaining)
+ )
}
}
}
-
- return null
}
/**
* Verifies the input and unlocks the locked sim with a puk code instead of pin.
*
* This occurs after incorrectly verifying the sim pin multiple times.
- *
- * @return Any message that should show associated with the provided input. Null means that no
- * message needs to be shown.
*/
- private suspend fun verifySimPuk(entry: String): String? {
+ private suspend fun verifySimPuk(entry: String) {
val (enteredSimPuk, enteredSimPin) = repository.simPukInputModel
val subscriptionId: Int = repository.subscriptionId.value
@@ -224,10 +221,11 @@
if (enteredSimPuk == null) {
if (entry.length >= MIN_SIM_PUK_LENGTH) {
repository.setSimPukUserInput(enteredSimPuk = entry)
- return resources.getString(R.string.kg_puk_enter_pin_hint)
+ _bouncerMessageChanged.emit(resources.getString(R.string.kg_puk_enter_pin_hint))
} else {
- return resources.getString(R.string.kg_invalid_sim_puk_hint)
+ _bouncerMessageChanged.emit(resources.getString(R.string.kg_invalid_sim_puk_hint))
}
+ return
}
// Stage 2: Set a new sim pin to lock the sim card.
@@ -237,10 +235,11 @@
enteredSimPuk = enteredSimPuk,
enteredSimPin = entry,
)
- return resources.getString(R.string.kg_enter_confirm_pin_hint)
+ _bouncerMessageChanged.emit(resources.getString(R.string.kg_enter_confirm_pin_hint))
} else {
- return resources.getString(R.string.kg_invalid_sim_pin_hint)
+ _bouncerMessageChanged.emit(resources.getString(R.string.kg_invalid_sim_pin_hint))
}
+ return
}
// Stage 3: Confirm the newly set sim pin.
@@ -250,7 +249,8 @@
resources.getString(R.string.kg_invalid_confirm_pin_hint)
)
repository.setSimPukUserInput(enteredSimPuk = enteredSimPuk)
- return resources.getString(R.string.kg_puk_enter_pin_hint)
+ _bouncerMessageChanged.emit(resources.getString(R.string.kg_puk_enter_pin_hint))
+ return
}
val result =
@@ -261,9 +261,11 @@
resetSimPukUserInput()
when (result.result) {
- PinResult.PIN_RESULT_TYPE_SUCCESS ->
+ PinResult.PIN_RESULT_TYPE_SUCCESS -> {
keyguardUpdateMonitor.reportSimUnlocked(subscriptionId)
- PinResult.PIN_RESULT_TYPE_INCORRECT ->
+ _bouncerMessageChanged.emit(null)
+ }
+ PinResult.PIN_RESULT_TYPE_INCORRECT -> {
if (result.attemptsRemaining <= CRITICAL_NUM_OF_ATTEMPTS) {
// Show a dialog to display the remaining number of attempts to verify the sim
// puk to the user.
@@ -274,17 +276,21 @@
isEsimLocked = repository.isLockedEsim.value == true
)
)
+ _bouncerMessageChanged.emit(null)
} else {
- return getPukPasswordErrorMessage(
- result.attemptsRemaining,
- isDefault = false,
- isEsimLocked = repository.isLockedEsim.value == true
+ _bouncerMessageChanged.emit(
+ getPukPasswordErrorMessage(
+ result.attemptsRemaining,
+ isDefault = false,
+ isEsimLocked = repository.isLockedEsim.value == true
+ )
)
}
- else -> return resources.getString(R.string.kg_password_puk_failed)
+ }
+ else -> {
+ _bouncerMessageChanged.emit(resources.getString(R.string.kg_password_puk_failed))
+ }
}
-
- return null
}
private fun getPinPasswordErrorMessage(attemptsRemaining: Int): String {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageStrings.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageStrings.kt
new file mode 100644
index 0000000..cb12ce5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageStrings.kt
@@ -0,0 +1,267 @@
+/*
+ * 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.bouncer.shared.model
+
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin
+import com.android.systemui.res.R
+
+typealias BouncerMessagePair = Pair<Int, Int>
+
+val BouncerMessagePair.primaryMessage: Int
+ get() = this.first
+
+val BouncerMessagePair.secondaryMessage: Int
+ get() = this.second
+
+object BouncerMessageStrings {
+ private val EmptyMessage = Pair(0, 0)
+
+ fun defaultMessage(
+ securityMode: AuthenticationMethodModel,
+ fpAuthIsAllowed: Boolean
+ ): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), 0)
+ Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), 0)
+ Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), 0)
+ else -> EmptyMessage
+ }
+ }
+
+ fun incorrectSecurityInput(
+ securityMode: AuthenticationMethodModel,
+ fpAuthIsAllowed: Boolean
+ ): BouncerMessagePair {
+ val secondaryMessage = incorrectSecurityInputSecondaryMessage(fpAuthIsAllowed)
+ return when (securityMode) {
+ Pattern -> Pair(R.string.kg_wrong_pattern_try_again, secondaryMessage)
+ Password -> Pair(R.string.kg_wrong_password_try_again, secondaryMessage)
+ Pin -> Pair(R.string.kg_wrong_pin_try_again, secondaryMessage)
+ else -> EmptyMessage
+ }
+ }
+
+ private fun incorrectSecurityInputSecondaryMessage(fpAuthIsAllowed: Boolean): Int {
+ return if (fpAuthIsAllowed) R.string.kg_wrong_input_try_fp_suggestion else 0
+ }
+
+ fun incorrectFingerprintInput(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+ val primaryMessage = R.string.kg_fp_not_recognized
+ return when (securityMode) {
+ Pattern -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pattern)
+ Password -> Pair(primaryMessage, R.string.kg_bio_try_again_or_password)
+ Pin -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pin)
+ else -> EmptyMessage
+ }
+ }
+
+ fun incorrectFaceInput(
+ securityMode: AuthenticationMethodModel,
+ fpAuthIsAllowed: Boolean
+ ): BouncerMessagePair {
+ return if (fpAuthIsAllowed) incorrectFaceInputWithFingerprintAllowed(securityMode)
+ else {
+ val primaryMessage = R.string.bouncer_face_not_recognized
+ when (securityMode) {
+ Pattern -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pattern)
+ Password -> Pair(primaryMessage, R.string.kg_bio_try_again_or_password)
+ Pin -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pin)
+ else -> EmptyMessage
+ }
+ }
+ }
+
+ private fun incorrectFaceInputWithFingerprintAllowed(
+ securityMode: AuthenticationMethodModel
+ ): BouncerMessagePair {
+ val secondaryMsg = R.string.bouncer_face_not_recognized
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(true), secondaryMsg)
+ Password -> Pair(passwordDefaultMessage(true), secondaryMsg)
+ Pin -> Pair(pinDefaultMessage(true), secondaryMsg)
+ else -> EmptyMessage
+ }
+ }
+
+ fun authRequiredAfterReboot(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_reason_restart_pattern)
+ Password ->
+ Pair(passwordDefaultMessage(false), R.string.kg_prompt_reason_restart_password)
+ Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_reason_restart_pin)
+ else -> EmptyMessage
+ }
+ }
+
+ fun authRequiredAfterAdminLockdown(
+ securityMode: AuthenticationMethodModel
+ ): BouncerMessagePair {
+ val secondaryMsg = R.string.kg_prompt_after_dpm_lock
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(false), secondaryMsg)
+ Password -> Pair(passwordDefaultMessage(false), secondaryMsg)
+ Pin -> Pair(pinDefaultMessage(false), secondaryMsg)
+ else -> EmptyMessage
+ }
+ }
+
+ fun authRequiredAfterAdaptiveAuthRequest(
+ securityMode: AuthenticationMethodModel,
+ fpAuthIsAllowed: Boolean
+ ): BouncerMessagePair {
+ val secondaryMsg = R.string.kg_prompt_after_adaptive_auth_lock
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ else -> EmptyMessage
+ }
+ }
+
+ fun authRequiredAfterUserLockdown(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern ->
+ Pair(patternDefaultMessage(false), R.string.kg_prompt_after_user_lockdown_pattern)
+ Password ->
+ Pair(passwordDefaultMessage(false), R.string.kg_prompt_after_user_lockdown_password)
+ Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_after_user_lockdown_pin)
+ else -> EmptyMessage
+ }
+ }
+
+ fun authRequiredForUnattendedUpdate(
+ securityMode: AuthenticationMethodModel
+ ): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_added_security_pattern)
+ Password ->
+ Pair(passwordDefaultMessage(false), R.string.kg_prompt_added_security_password)
+ Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_added_security_pin)
+ else -> EmptyMessage
+ }
+ }
+
+ fun authRequiredForMainlineUpdate(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_after_update_pattern)
+ Password ->
+ Pair(passwordDefaultMessage(false), R.string.kg_prompt_after_update_password)
+ Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_after_update_pin)
+ else -> EmptyMessage
+ }
+ }
+
+ fun authRequiredAfterPrimaryAuthTimeout(
+ securityMode: AuthenticationMethodModel
+ ): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_pattern_auth_timeout)
+ Password ->
+ Pair(passwordDefaultMessage(false), R.string.kg_prompt_password_auth_timeout)
+ Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_pin_auth_timeout)
+ else -> EmptyMessage
+ }
+ }
+
+ fun nonStrongAuthTimeout(
+ securityMode: AuthenticationMethodModel,
+ fpAuthIsAllowed: Boolean
+ ): BouncerMessagePair {
+ val secondaryMsg = R.string.kg_prompt_auth_timeout
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ else -> EmptyMessage
+ }
+ }
+
+ fun faceLockedOut(
+ securityMode: AuthenticationMethodModel,
+ fpAuthIsAllowed: Boolean
+ ): BouncerMessagePair {
+ val secondaryMsg = R.string.kg_face_locked_out
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ else -> EmptyMessage
+ }
+ }
+
+ fun class3AuthLockedOut(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(false), R.string.kg_bio_too_many_attempts_pattern)
+ Password ->
+ Pair(passwordDefaultMessage(false), R.string.kg_bio_too_many_attempts_password)
+ Pin -> Pair(pinDefaultMessage(false), R.string.kg_bio_too_many_attempts_pin)
+ else -> EmptyMessage
+ }
+ }
+
+ fun trustAgentDisabled(
+ securityMode: AuthenticationMethodModel,
+ fpAuthIsAllowed: Boolean
+ ): BouncerMessagePair {
+ val secondaryMsg = R.string.kg_trust_agent_disabled
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ else -> EmptyMessage
+ }
+ }
+
+ fun primaryAuthLockedOut(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern ->
+ Pair(
+ R.string.kg_too_many_failed_attempts_countdown,
+ R.string.kg_primary_auth_locked_out_pattern
+ )
+ Password ->
+ Pair(
+ R.string.kg_too_many_failed_attempts_countdown,
+ R.string.kg_primary_auth_locked_out_password
+ )
+ Pin ->
+ Pair(
+ R.string.kg_too_many_failed_attempts_countdown,
+ R.string.kg_primary_auth_locked_out_pin
+ )
+ else -> EmptyMessage
+ }
+ }
+
+ private fun patternDefaultMessage(fingerprintAllowed: Boolean): Int {
+ return if (fingerprintAllowed) R.string.kg_unlock_with_pattern_or_fp
+ else R.string.keyguard_enter_pattern
+ }
+
+ private fun pinDefaultMessage(fingerprintAllowed: Boolean): Int {
+ return if (fingerprintAllowed) R.string.kg_unlock_with_pin_or_fp
+ else R.string.keyguard_enter_pin
+ }
+
+ private fun passwordDefaultMessage(fingerprintAllowed: Boolean): Int {
+ return if (fingerprintAllowed) R.string.kg_unlock_with_password_or_fp
+ else R.string.keyguard_enter_password
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index 7f4a029..e910a92 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -157,8 +157,7 @@
if (authenticationMethod == AuthenticationMethodModel.Sim) {
viewModelScope.launch {
isSimUnlockingDialogVisible.value = true
- val msg = simBouncerInteractor.verifySim(getInput())
- interactor.setMessage(msg)
+ simBouncerInteractor.verifySim(getInput())
isSimUnlockingDialogVisible.value = false
clearInput()
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index 3819e61..4f4f3d0 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -97,6 +97,14 @@
}
};
+ private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
+ new KeyguardStateController.Callback() {
+ @Override
+ public void onKeyguardShowingChanged() {
+ updateSensorRegistration();
+ }
+ };
+
private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
new KeyguardUpdateMonitorCallback() {
@@ -176,6 +184,8 @@
mStatusBarStateController.addCallback(mStatusBarStateListener);
mState = mStatusBarStateController.getState();
+ mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
+
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
mJavaAdapter.alwaysCollectFlow(
@@ -364,6 +374,24 @@
} else {
sessionEnd();
}
+ updateSensorRegistration();
+ }
+
+ private boolean shouldBeRegisteredToSensors() {
+ return mScreenOn
+ && (mState == StatusBarState.KEYGUARD
+ || (mState == StatusBarState.SHADE
+ && mKeyguardStateController.isOccluded()
+ && mKeyguardStateController.isShowing()))
+ && !mShowingAod;
+ }
+
+ private void updateSensorRegistration() {
+ if (shouldBeRegisteredToSensors()) {
+ registerSensors();
+ } else {
+ unregisterSensors();
+ }
}
private void sessionStart() {
@@ -371,7 +399,6 @@
logDebug("Starting Session");
mSessionStarted = true;
mFalsingDataProvider.setJustUnlockedWithFace(false);
- registerSensors();
mFalsingDataProvider.onSessionStarted();
}
}
@@ -380,7 +407,6 @@
if (mSessionStarted) {
logDebug("Ending Session");
mSessionStarted = false;
- unregisterSensors();
mFalsingDataProvider.onSessionEnd();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
new file mode 100644
index 0000000..9e7fb4e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal
+
+import android.annotation.SuppressLint
+import android.app.DreamManager
+import com.android.systemui.CoreStartable
+import com.android.systemui.Flags.communalHub
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.Utils.Companion.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+/**
+ * A [CoreStartable] responsible for automatically starting the dream when the communal hub is
+ * shown, to support the user swiping away the hub to enter the dream.
+ */
+@SysUISingleton
+class CommunalDreamStartable
+@Inject
+constructor(
+ private val powerInteractor: PowerInteractor,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ private val dreamManager: DreamManager,
+ @Background private val bgScope: CoroutineScope,
+) : CoreStartable {
+ @SuppressLint("MissingPermission")
+ override fun start() {
+ if (!communalHub()) {
+ return
+ }
+
+ // Restart the dream underneath the hub in order to support the ability to swipe
+ // away the hub to enter the dream.
+ keyguardTransitionInteractor.finishedKeyguardState
+ .sample(powerInteractor.isAwake, keyguardInteractor.isDreaming)
+ .onEach { (finishedState, isAwake, dreaming) ->
+ if (
+ finishedState == KeyguardState.GLANCEABLE_HUB &&
+ !dreaming &&
+ dreamManager.canStartDreaming(isAwake)
+ ) {
+ dreamManager.startDream()
+ }
+ }
+ .launchIn(bgScope)
+ }
+}
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 8142957..940b48c 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
@@ -18,9 +18,14 @@
import android.app.smartspace.SmartspaceTarget
import android.content.ComponentName
+import android.content.Intent
+import android.content.IntentFilter
import android.os.UserHandle
+import android.os.UserManager
+import android.provider.Settings
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.communal.data.repository.CommunalMediaRepository
import com.android.systemui.communal.data.repository.CommunalPrefsRepository
import com.android.systemui.communal.data.repository.CommunalRepository
@@ -45,6 +50,7 @@
import com.android.systemui.log.dagger.CommunalTableLog
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
@@ -53,6 +59,7 @@
import com.android.systemui.util.kotlin.BooleanFlowOperators.and
import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import com.android.systemui.util.kotlin.BooleanFlowOperators.or
+import com.android.systemui.util.kotlin.emitOnStart
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -78,6 +85,7 @@
@Inject
constructor(
@Application applicationScope: CoroutineScope,
+ broadcastDispatcher: BroadcastDispatcher,
private val communalRepository: CommunalRepository,
private val widgetRepository: CommunalWidgetRepository,
private val communalPrefsRepository: CommunalPrefsRepository,
@@ -88,6 +96,8 @@
private val appWidgetHost: CommunalAppWidgetHost,
private val editWidgetsActivityStarter: EditWidgetsActivityStarter,
private val userTracker: UserTracker,
+ private val activityStarter: ActivityStarter,
+ private val userManager: UserManager,
sceneInteractor: SceneInteractor,
sceneContainerFlags: SceneContainerFlags,
@CommunalLog logBuffer: LogBuffer,
@@ -247,6 +257,18 @@
editWidgetsActivityStarter.startActivity(preselectedKey)
}
+ /**
+ * Navigates to communal widget setting after user has unlocked the device. Currently, this
+ * setting resides within the Hub Mode settings screen.
+ */
+ fun navigateToCommunalWidgetSettings() {
+ activityStarter.postStartActivityDismissingKeyguard(
+ Intent(Settings.ACTION_COMMUNAL_SETTING)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP),
+ /* delay= */ 0,
+ )
+ }
+
/** Dismiss the CTA tile from the hub in view mode. */
suspend fun dismissCtaTile() = communalPrefsRepository.setCtaDismissedForCurrentUser()
@@ -272,6 +294,33 @@
fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) =
widgetRepository.updateWidgetOrder(widgetIdToPriorityMap)
+ /** Request to unpause work profile that is currently in quiet mode. */
+ fun unpauseWorkProfile() {
+ userTracker.userProfiles
+ .find { it.isManagedProfile }
+ ?.userHandle
+ ?.let { userHandle ->
+ userManager.requestQuietModeEnabled(/* enableQuietMode */ false, userHandle)
+ }
+ }
+
+ /** Returns true if work profile is in quiet mode (disabled) for user handle. */
+ private fun isQuietModeEnabled(userHandle: UserHandle): Boolean =
+ userManager.isManagedProfile(userHandle.identifier) &&
+ userManager.isQuietModeEnabled(userHandle)
+
+ /** Emits whenever a work profile pause or unpause broadcast is received. */
+ private val updateOnWorkProfileBroadcastReceived: Flow<Unit> =
+ broadcastDispatcher
+ .broadcastFlow(
+ filter =
+ IntentFilter().apply {
+ addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
+ addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
+ },
+ )
+ .emitOnStart()
+
/** All widgets present in db. */
val communalWidgets: Flow<List<CommunalWidgetContentModel>> =
isCommunalAvailable.flatMapLatest { available ->
@@ -282,8 +331,9 @@
val widgetContent: Flow<List<WidgetContent>> =
combine(
widgetRepository.communalWidgets.map { filterWidgetsByExistingUsers(it) },
- communalSettingsInteractor.communalWidgetCategories
- ) { widgets, allowedCategories ->
+ communalSettingsInteractor.communalWidgetCategories,
+ updateOnWorkProfileBroadcastReceived,
+ ) { widgets, allowedCategories, _ ->
widgets.map { widget ->
if (widget.providerInfo.widgetCategory and allowedCategories != 0) {
// At least one category this widget specified is allowed, so show it
@@ -291,6 +341,7 @@
appWidgetId = widget.appWidgetId,
providerInfo = widget.providerInfo,
appWidgetHost = appWidgetHost,
+ inQuietMode = isQuietModeEnabled(widget.providerInfo.profile)
)
} else {
WidgetContent.DisabledWidget(
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index 12576d4..5fabd3c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -51,6 +51,7 @@
override val appWidgetId: Int,
override val providerInfo: AppWidgetProviderInfo,
val appWidgetHost: CommunalAppWidgetHost,
+ val inQuietMode: Boolean,
) : WidgetContent {
override val key = KEY.widget(appWidgetId)
// Widget size is always half.
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 35372cd..85f3c20 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -70,6 +70,10 @@
communalInteractor.addWidget(componentName, user, priority, configurator)
}
+ open fun onOpenEnableWidgetDialog() {}
+
+ open fun onOpenEnableWorkProfileDialog() {}
+
/** A list of all the communal content to be displayed in the communal hub. */
abstract val communalContent: Flow<List<CommunalContentModel>>
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 35b27aa..6e69ed7 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -87,6 +87,14 @@
override val isPopupOnDismissCtaShowing: Flow<Boolean> =
_isPopupOnDismissCtaShowing.asStateFlow()
+ private val _isEnableWidgetDialogShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ val isEnableWidgetDialogShowing: Flow<Boolean> = _isEnableWidgetDialogShowing.asStateFlow()
+
+ private val _isEnableWorkProfileDialogShowing: MutableStateFlow<Boolean> =
+ MutableStateFlow(false)
+ val isEnableWorkProfileDialogShowing: Flow<Boolean> =
+ _isEnableWorkProfileDialogShowing.asStateFlow()
+
/** Whether touches should be disabled in communal */
val touchesAllowed: Flow<Boolean> = not(shadeInteractor.isAnyFullyExpanded)
@@ -120,6 +128,40 @@
setPopupOnDismissCtaVisibility(false)
}
+ override fun onOpenEnableWidgetDialog() {
+ setIsEnableWidgetDialogShowing(true)
+ }
+
+ fun onEnableWidgetDialogConfirm() {
+ communalInteractor.navigateToCommunalWidgetSettings()
+ setIsEnableWidgetDialogShowing(false)
+ }
+
+ fun onEnableWidgetDialogCancel() {
+ setIsEnableWidgetDialogShowing(false)
+ }
+
+ override fun onOpenEnableWorkProfileDialog() {
+ setIsEnableWorkProfileDialogShowing(true)
+ }
+
+ fun onEnableWorkProfileDialogConfirm() {
+ communalInteractor.unpauseWorkProfile()
+ setIsEnableWorkProfileDialogShowing(false)
+ }
+
+ fun onEnableWorkProfileDialogCancel() {
+ setIsEnableWorkProfileDialogShowing(false)
+ }
+
+ private fun setIsEnableWidgetDialogShowing(isVisible: Boolean) {
+ _isEnableWidgetDialogShowing.value = isVisible
+ }
+
+ private fun setIsEnableWorkProfileDialogShowing(isVisible: Boolean) {
+ _isEnableWorkProfileDialogShowing.value = isVisible
+ }
+
private fun setPopupOnDismissCtaVisibility(isVisible: Boolean) {
_isPopupOnDismissCtaShowing.value = isVisible
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index a4011fd..1003050 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -69,6 +69,7 @@
import com.android.systemui.toast.ToastModule;
import com.android.systemui.unfold.SysUIUnfoldStartableModule;
import com.android.systemui.unfold.UnfoldTransitionModule;
+import com.android.systemui.util.kotlin.SysUICoroutinesModule;
import com.android.systemui.volume.dagger.VolumeModule;
import com.android.systemui.wallpapers.dagger.WallpaperModule;
@@ -117,6 +118,7 @@
ShadeModule.class,
StartCentralSurfacesModule.class,
SceneContainerFrameworkModule.class,
+ SysUICoroutinesModule.class,
SysUIUnfoldStartableModule.class,
UnfoldTransitionModule.Startables.class,
ToastModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index a3d6ad4..21ee5bd 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -24,6 +24,7 @@
import com.android.systemui.back.domain.interactor.BackActionInteractor
import com.android.systemui.biometrics.BiometricNotificationService
import com.android.systemui.clipboardoverlay.ClipboardListener
+import com.android.systemui.communal.CommunalDreamStartable
import com.android.systemui.communal.CommunalSceneStartable
import com.android.systemui.communal.log.CommunalLoggerStartable
import com.android.systemui.communal.widgets.CommunalAppWidgetHostStartable
@@ -328,6 +329,11 @@
@Binds
@IntoMap
+ @ClassKey(CommunalDreamStartable::class)
+ abstract fun bindCommunalDreamStartable(impl: CommunalDreamStartable): CoreStartable
+
+ @Binds
+ @IntoMap
@ClassKey(CommunalAppWidgetHostStartable::class)
abstract fun bindCommunalAppWidgetHostStartable(
impl: CommunalAppWidgetHostStartable
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt
index 96171aa..d495fac 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt
@@ -18,6 +18,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.shared.model.AuthenticationFlags
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -31,9 +32,22 @@
constructor(
repository: BiometricSettingsRepository,
) {
+
+ /**
+ * Flags that control the device entry authentication behavior.
+ *
+ * This exposes why biometrics may not be currently allowed.
+ */
+ val authenticationFlags: Flow<AuthenticationFlags> = repository.authenticationFlags
+
+ /** Whether the current user has enrolled and enabled fingerprint auth. */
+ val isFingerprintAuthEnrolledAndEnabled: Flow<Boolean> =
+ repository.isFingerprintEnrolledAndEnabled
+
val fingerprintAuthCurrentlyAllowed: Flow<Boolean> =
repository.isFingerprintAuthCurrentlyAllowed
- val faceAuthEnrolledAndEnabled: Flow<Boolean> = repository.isFaceAuthEnrolledAndEnabled
+ /** Whether the current user has enrolled and enabled face auth. */
+ val isFaceAuthEnrolledAndEnabled: Flow<Boolean> = repository.isFaceAuthEnrolledAndEnabled
val faceAuthCurrentlyAllowed: Flow<Boolean> = repository.isFaceAuthCurrentlyAllowed
/** Whether both fingerprint and face are enrolled and enabled for device entry. */
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
index 99bd25b..7733de4 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
@@ -32,6 +32,10 @@
/** Current detection status */
val detectionStatus: Flow<FaceDetectionStatus>
+ val lockedOut: Flow<Boolean>
+
+ val authenticated: Flow<Boolean>
+
/** Can face auth be run right now */
fun canFaceAuthRun(): Boolean
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
index a5f6f7c..8059993 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
@@ -16,6 +16,8 @@
package com.android.systemui.deviceentry.domain.interactor
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
@@ -23,14 +25,20 @@
import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.map
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class DeviceEntryFingerprintAuthInteractor
@Inject
constructor(
repository: DeviceEntryFingerprintAuthRepository,
+ biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
+ fingerprintPropertyRepository: FingerprintPropertyRepository,
) {
/** Whether fingerprint authentication is currently running or not */
val isRunning: Flow<Boolean> = repository.isRunning
@@ -47,4 +55,21 @@
repository.authenticationStatus.filterIsInstance<ErrorFingerprintAuthenticationStatus>()
val fingerprintHelp: Flow<HelpFingerprintAuthenticationStatus> =
repository.authenticationStatus.filterIsInstance<HelpFingerprintAuthenticationStatus>()
+
+ /**
+ * Whether fingerprint authentication is currently allowed for the user. This is true if the
+ * user has fingerprint auth enabled, enrolled, it is not disabled by any security timeouts by
+ * [com.android.systemui.keyguard.shared.model.AuthenticationFlags] and not locked out due to
+ * too many incorrect attempts.
+ */
+ val isFingerprintAuthCurrentlyAllowed: Flow<Boolean> =
+ combine(isLockedOut, biometricSettingsInteractor.fingerprintAuthCurrentlyAllowed, ::Pair)
+ .map { (lockedOut, currentlyAllowed) -> !lockedOut && currentlyAllowed }
+
+ /**
+ * Whether the fingerprint sensor is present under the display as opposed to being on the power
+ * button or behind/rear of the phone.
+ */
+ val isSensorUnderDisplay =
+ fingerprintPropertyRepository.sensorType.map(FingerprintSensorType::isUdfps)
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 029a4f3..fa2421a 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -16,24 +16,30 @@
package com.android.systemui.deviceentry.domain.interactor
+import androidx.annotation.VisibleForTesting
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
-import com.android.systemui.keyguard.data.repository.TrustRepository
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
+import com.android.systemui.flags.SystemPropertiesHelper
+import com.android.systemui.keyguard.domain.interactor.TrustInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.util.kotlin.Quad
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
@@ -55,10 +61,13 @@
private val repository: DeviceEntryRepository,
private val authenticationInteractor: AuthenticationInteractor,
private val sceneInteractor: SceneInteractor,
- deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository,
- trustRepository: TrustRepository,
+ faceAuthInteractor: DeviceEntryFaceAuthInteractor,
+ private val fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
+ private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
+ private val trustInteractor: TrustInteractor,
flags: SceneContainerFlags,
deviceUnlockedInteractor: DeviceUnlockedInteractor,
+ private val systemPropertiesHelper: SystemPropertiesHelper,
) {
/**
* Whether the device is unlocked.
@@ -96,8 +105,8 @@
*/
private val isPassivelyAuthenticated =
merge(
- trustRepository.isCurrentUserTrusted,
- deviceEntryFaceAuthRepository.isAuthenticated,
+ trustInteractor.isTrusted,
+ faceAuthInteractor.authenticated,
)
.onStart { emit(false) }
@@ -134,6 +143,67 @@
initialValue = null,
)
+ private val faceEnrolledAndEnabled = biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled
+ private val fingerprintEnrolledAndEnabled =
+ biometricSettingsInteractor.isFingerprintAuthEnrolledAndEnabled
+ private val trustAgentEnabled = trustInteractor.isEnrolledAndEnabled
+
+ private val faceOrFingerprintOrTrustEnabled: Flow<Triple<Boolean, Boolean, Boolean>> =
+ combine(faceEnrolledAndEnabled, fingerprintEnrolledAndEnabled, trustAgentEnabled, ::Triple)
+
+ /**
+ * Reason why device entry is restricted to certain authentication methods for the current user.
+ *
+ * Emits null when there are no device entry restrictions active.
+ */
+ val deviceEntryRestrictionReason: Flow<DeviceEntryRestrictionReason?> =
+ faceOrFingerprintOrTrustEnabled.flatMapLatest {
+ (faceEnabled, fingerprintEnabled, trustEnabled) ->
+ if (faceEnabled || fingerprintEnabled || trustEnabled) {
+ combine(
+ biometricSettingsInteractor.authenticationFlags,
+ faceAuthInteractor.lockedOut,
+ fingerprintAuthInteractor.isLockedOut,
+ trustInteractor.isTrustAgentCurrentlyAllowed,
+ ::Quad
+ )
+ .map { (authFlags, isFaceLockedOut, isFingerprintLockedOut, trustManaged) ->
+ when {
+ authFlags.isPrimaryAuthRequiredAfterReboot &&
+ wasRebootedForMainlineUpdate ->
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate
+ authFlags.isPrimaryAuthRequiredAfterReboot ->
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot
+ authFlags.isPrimaryAuthRequiredAfterDpmLockdown ->
+ DeviceEntryRestrictionReason.PolicyLockdown
+ authFlags.isInUserLockdown -> DeviceEntryRestrictionReason.UserLockdown
+ authFlags.isPrimaryAuthRequiredForUnattendedUpdate ->
+ DeviceEntryRestrictionReason.UnattendedUpdate
+ authFlags.isPrimaryAuthRequiredAfterTimeout ->
+ DeviceEntryRestrictionReason.SecurityTimeout
+ authFlags.isPrimaryAuthRequiredAfterLockout ->
+ DeviceEntryRestrictionReason.BouncerLockedOut
+ isFingerprintLockedOut ->
+ DeviceEntryRestrictionReason.StrongBiometricsLockedOut
+ isFaceLockedOut && faceAuthInteractor.isFaceAuthStrong() ->
+ DeviceEntryRestrictionReason.StrongBiometricsLockedOut
+ isFaceLockedOut -> DeviceEntryRestrictionReason.NonStrongFaceLockedOut
+ authFlags.isSomeAuthRequiredAfterAdaptiveAuthRequest ->
+ DeviceEntryRestrictionReason.AdaptiveAuthRequest
+ (trustEnabled && !trustManaged) &&
+ (authFlags.someAuthRequiredAfterTrustAgentExpired ||
+ authFlags.someAuthRequiredAfterUserRequest) ->
+ DeviceEntryRestrictionReason.TrustAgentDisabled
+ authFlags.strongerAuthRequiredAfterNonStrongBiometricsTimeout ->
+ DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout
+ else -> null
+ }
+ }
+ } else {
+ flowOf(null)
+ }
+ }
+
/**
* Attempt to enter the device and dismiss the lockscreen. If authentication is required to
* unlock the device it will transition to bouncer.
@@ -187,4 +257,12 @@
}
}
}
+
+ private val wasRebootedForMainlineUpdate
+ get() = systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE
+
+ companion object {
+ @VisibleForTesting const val SYS_BOOT_REASON_PROP = "sys.boot.reason.last"
+ @VisibleForTesting const val REBOOT_MAINLINE_UPDATE = "reboot,mainline_update"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt
index fd6fbc9..98deda09 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt
@@ -77,7 +77,7 @@
private fun startUpdatingFaceHelpMessageDeferral() {
scope.launch {
- biometricSettingsInteractor.faceAuthEnrolledAndEnabled
+ biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled
.flatMapLatest { faceEnrolledAndEnabled ->
if (faceEnrolledAndEnabled) {
faceAcquired
@@ -94,7 +94,7 @@
}
scope.launch {
- biometricSettingsInteractor.faceAuthEnrolledAndEnabled
+ biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled
.flatMapLatest { faceEnrolledAndEnabled ->
if (faceEnrolledAndEnabled) {
faceHelp
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
index 3b94166..65f3eb7 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
@@ -31,10 +31,10 @@
*/
@SysUISingleton
class NoopDeviceEntryFaceAuthInteractor @Inject constructor() : DeviceEntryFaceAuthInteractor {
- override val authenticationStatus: Flow<FaceAuthenticationStatus>
- get() = emptyFlow()
- override val detectionStatus: Flow<FaceDetectionStatus>
- get() = emptyFlow()
+ override val authenticationStatus: Flow<FaceAuthenticationStatus> = emptyFlow()
+ override val detectionStatus: Flow<FaceDetectionStatus> = emptyFlow()
+ override val lockedOut: Flow<Boolean> = emptyFlow()
+ override val authenticated: Flow<Boolean> = emptyFlow()
override fun canFaceAuthRun(): Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index 0c9fbc2..a7266503 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -272,6 +272,8 @@
/** Provide the status of face detection */
override val detectionStatus = repository.detectionStatus
+ override val lockedOut: Flow<Boolean> = repository.isLockedOut
+ override val authenticated: Flow<Boolean> = repository.isAuthenticated
private fun runFaceAuth(uiEvent: FaceAuthUiEvent, fallbackToDetect: Boolean) {
if (repository.isLockedOut.value) {
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceEntryRestrictionReason.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceEntryRestrictionReason.kt
new file mode 100644
index 0000000..5b672ac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceEntryRestrictionReason.kt
@@ -0,0 +1,119 @@
+/*
+ * 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.deviceentry.shared.model
+
+/** List of reasons why device entry can be restricted to certain authentication methods. */
+enum class DeviceEntryRestrictionReason {
+ /**
+ * Reason: Lockdown initiated by the user.
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ */
+ UserLockdown,
+
+ /**
+ * Reason: Not unlocked since reboot.
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ */
+ DeviceNotUnlockedSinceReboot,
+
+ /**
+ * Reason: Not unlocked since reboot after a mainline update.
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ */
+ DeviceNotUnlockedSinceMainlineUpdate,
+
+ /**
+ * Reason: Lockdown initiated by admin through installed device policy
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ */
+ PolicyLockdown,
+
+ /**
+ * Reason: Device entry credentials need to be used for an unattended update at a later point in
+ * time.
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ */
+ UnattendedUpdate,
+
+ /**
+ * Reason: Device was not unlocked using PIN/Pattern/Password for a prolonged period of time.
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ */
+ SecurityTimeout,
+
+ /**
+ * Reason: A "class 3"/strong biometrics device entry method was locked out after many incorrect
+ * authentication attempts.
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ *
+ * @see
+ * [Biometric classes](https://source.android.com/docs/security/features/biometric/measure#biometric-classes)
+ */
+ StrongBiometricsLockedOut,
+
+ /**
+ * Reason: A weak (class 2)/convenience (class 3) strength face biometrics device entry method
+ * was locked out after many incorrect authentication attempts.
+ *
+ * Restriction: Only stronger authentication methods (class 3 or bouncer) are allowed.
+ *
+ * @see
+ * [Biometric classes](https://source.android.com/docs/security/features/biometric/measure#biometric-classes)
+ */
+ NonStrongFaceLockedOut,
+
+ /**
+ * Reason: Device was last unlocked using a weak/convenience strength biometrics device entry
+ * method and a stronger authentication method wasn't used to unlock the device for a prolonged
+ * period of time.
+ *
+ * Restriction: Only stronger authentication methods (class 3 or bouncer) are allowed.
+ *
+ * @see
+ * [Biometric classes](https://source.android.com/docs/security/features/biometric/measure#biometric-classes)
+ */
+ NonStrongBiometricsSecurityTimeout,
+
+ /**
+ * Reason: A trust agent that was granting trust has either expired or disabled by the user by
+ * opening the power menu.
+ *
+ * Restriction: Only non trust agent device entry methods are allowed.
+ */
+ TrustAgentDisabled,
+
+ /**
+ * Reason: Theft protection is enabled after too many unlock attempts.
+ *
+ * Restriction: Only stronger authentication methods (class 3 or bouncer) are allowed.
+ */
+ AdaptiveAuthRequest,
+
+ /**
+ * Reason: Bouncer was locked out after too many incorrect authentication attempts.
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ */
+ BouncerLockedOut,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index b97bace..f860893 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -33,7 +33,7 @@
import com.android.systemui.complication.ComplicationLayoutParams.POSITION_TOP
import com.android.systemui.complication.ComplicationLayoutParams.Position
import com.android.systemui.dreams.dagger.DreamOverlayModule
-import com.android.systemui.dreams.ui.viewmodel.DreamOverlayViewModel
+import com.android.systemui.dreams.ui.viewmodel.DreamViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
@@ -53,7 +53,7 @@
private val mStatusBarViewController: DreamOverlayStatusBarViewController,
private val mOverlayStateController: DreamOverlayStateController,
@Named(DreamOverlayModule.DREAM_BLUR_RADIUS) private val mDreamBlurRadius: Int,
- private val dreamOverlayViewModel: DreamOverlayViewModel,
+ private val dreamViewModel: DreamViewModel,
@Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION)
private val mDreamInBlurAnimDurationMs: Long,
@Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
@@ -87,7 +87,7 @@
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
- dreamOverlayViewModel.dreamOverlayTranslationY.collect { px ->
+ dreamViewModel.dreamOverlayTranslationY.collect { px ->
ComplicationLayoutParams.iteratePositions(
{ position: Int -> setElementsTranslationYAtPosition(px, position) },
POSITION_TOP or POSITION_BOTTOM
@@ -96,7 +96,7 @@
}
launch {
- dreamOverlayViewModel.dreamOverlayTranslationX.collect { px ->
+ dreamViewModel.dreamOverlayTranslationX.collect { px ->
ComplicationLayoutParams.iteratePositions(
{ position: Int -> setElementsTranslationXAtPosition(px, position) },
POSITION_TOP or POSITION_BOTTOM
@@ -105,7 +105,7 @@
}
launch {
- dreamOverlayViewModel.dreamOverlayAlpha.collect { alpha ->
+ dreamViewModel.dreamOverlayAlpha.collect { alpha ->
ComplicationLayoutParams.iteratePositions(
{ position: Int ->
setElementsAlphaAtPosition(
@@ -120,7 +120,7 @@
}
launch {
- dreamOverlayViewModel.transitionEnded.collect { _ ->
+ dreamViewModel.transitionEnded.collect { _ ->
mOverlayStateController.setExitAnimationsRunning(false)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt
index 6cd94c6..1452526 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt
@@ -30,7 +30,7 @@
class HomeControlsDreamStartable
@Inject
constructor(
- private val context: Context,
+ context: Context,
private val packageManager: PackageManager,
private val homeControlsComponentInteractor: HomeControlsComponentInteractor,
@Background private val bgScope: CoroutineScope,
@@ -39,10 +39,13 @@
private val componentName = ComponentName(context, HomeControlsDreamService::class.java)
override fun start() {
- if (!homePanelDream()) return
bgScope.launch {
- homeControlsComponentInteractor.panelComponent.collect { selectedPanelComponent ->
- setEnableHomeControlPanel(selectedPanelComponent != null)
+ if (homePanelDream()) {
+ homeControlsComponentInteractor.panelComponent.collect { selectedPanelComponent ->
+ setEnableHomeControlPanel(selectedPanelComponent != null)
+ }
+ } else {
+ setEnableHomeControlPanel(false)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamOverlayViewModel.kt
deleted file mode 100644
index bd99f4b..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamOverlayViewModel.kt
+++ /dev/null
@@ -1,63 +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.dreams.ui.viewmodel
-
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
-import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToDreamingTransitionViewModel
-import com.android.systemui.res.R
-import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.merge
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SysUISingleton
-class DreamOverlayViewModel
-@Inject
-constructor(
- configurationInteractor: ConfigurationInteractor,
- toGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
- fromGlanceableHubTransitionInteractor: GlanceableHubToDreamingTransitionViewModel,
- private val toLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
-) {
-
- val dreamOverlayTranslationX: Flow<Float> =
- merge(
- toGlanceableHubTransitionViewModel.dreamOverlayTranslationX,
- fromGlanceableHubTransitionInteractor.dreamOverlayTranslationX,
- )
-
- val dreamOverlayTranslationY: Flow<Float> =
- configurationInteractor
- .dimensionPixelSize(R.dimen.dream_overlay_exit_y_offset)
- .flatMapLatest { px: Int ->
- toLockscreenTransitionViewModel.dreamOverlayTranslationY(px)
- }
-
- val dreamOverlayAlpha: Flow<Float> =
- merge(
- toLockscreenTransitionViewModel.dreamOverlayAlpha,
- toGlanceableHubTransitionViewModel.dreamOverlayAlpha,
- fromGlanceableHubTransitionInteractor.dreamOverlayAlpha,
- )
-
- val transitionEnded = toLockscreenTransitionViewModel.transitionEnded
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
new file mode 100644
index 0000000..0cb57fb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.dreams.ui.viewmodel
+
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dock.DockManager
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToDreamingTransitionViewModel
+import com.android.systemui.res.R
+import com.android.systemui.settings.UserTracker
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.merge
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class DreamViewModel
+@Inject
+constructor(
+ configurationInteractor: ConfigurationInteractor,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ fromGlanceableHubTransitionInteractor: GlanceableHubToDreamingTransitionViewModel,
+ private val toGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
+ private val toLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
+ private val dockManager: DockManager,
+ private val communalInteractor: CommunalInteractor,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val userTracker: UserTracker,
+) {
+
+ fun startTransitionFromDream() {
+ val showGlanceableHub =
+ dockManager.isDocked &&
+ communalInteractor.isCommunalEnabled.value &&
+ !keyguardUpdateMonitor.isEncryptedOrLockdown(userTracker.userId)
+ if (showGlanceableHub) {
+ toGlanceableHubTransitionViewModel.startTransition()
+ communalInteractor.onSceneChanged(CommunalScenes.Communal)
+ } else {
+ toLockscreenTransitionViewModel.startTransition()
+ }
+ }
+
+ val dreamOverlayTranslationX: Flow<Float> =
+ merge(
+ toGlanceableHubTransitionViewModel.dreamOverlayTranslationX,
+ fromGlanceableHubTransitionInteractor.dreamOverlayTranslationX,
+ )
+ .distinctUntilChanged()
+
+ val dreamOverlayTranslationY: Flow<Float> =
+ configurationInteractor
+ .dimensionPixelSize(R.dimen.dream_overlay_exit_y_offset)
+ .flatMapLatest { px: Int ->
+ toLockscreenTransitionViewModel.dreamOverlayTranslationY(px)
+ }
+
+ val dreamAlpha: Flow<Float> =
+ merge(
+ toLockscreenTransitionViewModel.dreamOverlayAlpha,
+ toGlanceableHubTransitionViewModel.dreamAlpha,
+ )
+ .distinctUntilChanged()
+
+ val dreamOverlayAlpha: Flow<Float> =
+ merge(
+ toLockscreenTransitionViewModel.dreamOverlayAlpha,
+ toGlanceableHubTransitionViewModel.dreamOverlayAlpha,
+ fromGlanceableHubTransitionInteractor.dreamOverlayAlpha,
+ )
+ .distinctUntilChanged()
+
+ val transitionEnded =
+ keyguardTransitionInteractor.fromDreamingTransition.filter { step ->
+ step.transitionState == TransitionState.FINISHED ||
+ step.transitionState == TransitionState.CANCELED
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 6bb84649..a199fea 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -76,23 +76,6 @@
val NOTIFICATION_MEMORY_LOGGING_ENABLED =
releasedFlag("notification_memory_logging_enabled")
- // TODO(b/260335638): Tracking Bug
- @JvmField
- val NOTIFICATION_INLINE_REPLY_ANIMATION = releasedFlag("notification_inline_reply_animation")
-
- // TODO(b/288326013): Tracking Bug
- @JvmField
- val NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION =
- unreleasedFlag("notification_async_hybrid_view_inflation", teamfood = false)
-
- @JvmField
- val ANIMATED_NOTIFICATION_SHADE_INSETS =
- releasedFlag("animated_notification_shade_insets")
-
- // TODO(b/268005230): Tracking Bug
- @JvmField
- val SENSITIVE_REVEAL_ANIM = releasedFlag("sensitive_reveal_anim")
-
// TODO(b/280783617): Tracking Bug
@Keep
@JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
index 6fa20de..1e3c604 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
@@ -20,36 +20,34 @@
import javax.inject.Inject
import javax.inject.Singleton
-/**
- * Proxy to make {@link SystemProperties} easily testable.
- */
+/** Proxy to make {@link SystemProperties} easily testable. */
@Singleton
open class SystemPropertiesHelper @Inject constructor() {
- fun get(name: String): String {
+ open fun get(name: String): String {
return SystemProperties.get(name)
}
- fun get(name: String, def: String?): String {
+ open fun get(name: String, def: String?): String {
return SystemProperties.get(name, def)
}
- fun getBoolean(name: String, default: Boolean): Boolean {
+ open fun getBoolean(name: String, default: Boolean): Boolean {
return SystemProperties.getBoolean(name, default)
}
- fun setBoolean(name: String, value: Boolean) {
+ open fun setBoolean(name: String, value: Boolean) {
SystemProperties.set(name, if (value) "1" else "0")
}
- fun set(name: String, value: String) {
+ open fun set(name: String, value: String) {
SystemProperties.set(name, value)
}
- fun set(name: String, value: Int) {
+ open fun set(name: String, value: Int) {
set(name, value.toString())
}
- fun erase(name: String) {
+ open fun erase(name: String) {
set(name, "")
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/LongPressHapticBuilder.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/LongPressHapticBuilder.kt
new file mode 100644
index 0000000..0143b85
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/LongPressHapticBuilder.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics.qs
+
+import android.os.VibrationEffect
+import android.util.Log
+import kotlin.math.max
+
+object LongPressHapticBuilder {
+
+ const val INVALID_DURATION = 0 /* in ms */
+
+ private const val TAG = "LongPressHapticBuilder"
+ private const val SPIN_SCALE = 0.2f
+ private const val CLICK_SCALE = 0.5f
+ private const val LOW_TICK_SCALE = 0.08f
+ private const val WARMUP_TIME = 75 /* in ms */
+ private const val DAMPING_TIME = 24 /* in ms */
+
+ /** Create the signal that indicates that a long-press action is available. */
+ fun createLongPressHint(
+ lowTickDuration: Int,
+ spinDuration: Int,
+ effectDuration: Int
+ ): VibrationEffect? {
+ if (lowTickDuration == 0 || spinDuration == 0) {
+ Log.d(
+ TAG,
+ "The LOW_TICK and/or SPIN primitives are not supported. No signal created.",
+ )
+ return null
+ }
+ if (effectDuration < WARMUP_TIME + spinDuration + DAMPING_TIME) {
+ Log.d(
+ TAG,
+ "Cannot fit long-press hint signal in the effect duration. No signal created",
+ )
+ return null
+ }
+
+ val nLowTicks = WARMUP_TIME / lowTickDuration
+ val rampDownLowTicks = DAMPING_TIME / lowTickDuration
+ val composition = VibrationEffect.startComposition()
+
+ // Warmup low ticks
+ repeat(nLowTicks) {
+ composition.addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+ LOW_TICK_SCALE,
+ 0,
+ )
+ }
+
+ // Spin effect
+ composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN, SPIN_SCALE, 0)
+
+ // Damping low ticks
+ repeat(rampDownLowTicks) { i ->
+ composition.addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+ LOW_TICK_SCALE / (i + 1),
+ 0,
+ )
+ }
+
+ return composition.compose()
+ }
+
+ /** Create a "snapping" effect that triggers at the end of a long-press gesture */
+ fun createSnapEffect(): VibrationEffect? =
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, CLICK_SCALE, 0)
+ .compose()
+
+ /** Creates a signal that indicates the reversal of the long-press animation. */
+ fun createReversedEffect(
+ pausedProgress: Float,
+ lowTickDuration: Int,
+ effectDuration: Int,
+ ): VibrationEffect? {
+ val duration = pausedProgress * effectDuration
+ if (duration == 0f) return null
+
+ if (lowTickDuration == 0) {
+ Log.d(TAG, "Cannot play reverse haptics because LOW_TICK is not supported")
+ return null
+ }
+
+ val nLowTicks = (duration / lowTickDuration).toInt()
+ if (nLowTicks == 0) return null
+
+ val composition = VibrationEffect.startComposition()
+ var scale: Float
+ val step = LOW_TICK_SCALE / nLowTicks
+ repeat(nLowTicks) { i ->
+ scale = max(LOW_TICK_SCALE - step * i, 0f)
+ composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, scale, 0)
+ }
+ return composition.compose()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
new file mode 100644
index 0000000..ec72a14
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -0,0 +1,237 @@
+/*
+ * 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.haptics.qs
+
+import android.animation.ValueAnimator
+import android.annotation.SuppressLint
+import android.os.VibrationEffect
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewConfiguration
+import android.view.animation.AccelerateDecelerateInterpolator
+import androidx.annotation.VisibleForTesting
+import androidx.core.animation.doOnCancel
+import androidx.core.animation.doOnEnd
+import androidx.core.animation.doOnStart
+import com.android.systemui.statusbar.VibratorHelper
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+
+/**
+ * A class that handles the long press visuo-haptic effect for a QS tile.
+ *
+ * The class is also a [View.OnTouchListener] to handle the touch events, clicks and long-press
+ * gestures of the tile. The class also provides a [State] that can be used to determine the current
+ * state of the long press effect.
+ *
+ * @property[vibratorHelper] The [VibratorHelper] to deliver haptic effects.
+ * @property[effectDuration] The duration of the effect in ms.
+ */
+class QSLongPressEffect(
+ private val vibratorHelper: VibratorHelper?,
+ private val effectDuration: Int,
+) : View.OnTouchListener {
+
+ /** Current state */
+ var state = State.IDLE
+ @VisibleForTesting set
+
+ /** Flows for view control and action */
+ private val _effectProgress = MutableStateFlow<Float?>(null)
+ val effectProgress = _effectProgress.asStateFlow()
+
+ private val _actionType = MutableStateFlow<ActionType?>(null)
+ val actionType = _actionType.asStateFlow()
+
+ /** Haptic effects */
+ private val durations =
+ vibratorHelper?.getPrimitiveDurations(
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+ VibrationEffect.Composition.PRIMITIVE_SPIN
+ )
+
+ private val longPressHint =
+ LongPressHapticBuilder.createLongPressHint(
+ durations?.get(0) ?: LongPressHapticBuilder.INVALID_DURATION,
+ durations?.get(1) ?: LongPressHapticBuilder.INVALID_DURATION,
+ effectDuration
+ )
+
+ private val snapEffect = LongPressHapticBuilder.createSnapEffect()
+
+ /* A coroutine scope and a timer job that waits for the pressedTimeout */
+ var scope: CoroutineScope? = null
+ private var waitJob: Job? = null
+
+ private val effectAnimator =
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = effectDuration.toLong()
+ interpolator = AccelerateDecelerateInterpolator()
+
+ doOnStart { handleAnimationStart() }
+ addUpdateListener { _effectProgress.value = animatedValue as Float }
+ doOnEnd { handleAnimationComplete() }
+ doOnCancel { handleAnimationCancel() }
+ }
+
+ private fun reverse() {
+ val pausedProgress = effectAnimator.animatedFraction
+ val effect =
+ LongPressHapticBuilder.createReversedEffect(
+ pausedProgress,
+ durations?.get(0) ?: 0,
+ effectDuration,
+ )
+ vibratorHelper?.cancel()
+ vibrate(effect)
+ effectAnimator.reverse()
+ }
+
+ private fun vibrate(effect: VibrationEffect?) {
+ if (vibratorHelper != null && effect != null) {
+ vibratorHelper.vibrate(effect)
+ }
+ }
+
+ /**
+ * Handle relevant touch events for the operation of a Tile.
+ *
+ * A click action is performed following the relevant logic that originates from the
+ * [MotionEvent.ACTION_UP] event depending on the current state.
+ */
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouch(view: View?, event: MotionEvent?): Boolean {
+ when (event?.actionMasked) {
+ MotionEvent.ACTION_DOWN -> handleActionDown()
+ MotionEvent.ACTION_UP -> handleActionUp()
+ MotionEvent.ACTION_CANCEL -> handleActionCancel()
+ }
+ return true
+ }
+
+ private fun handleActionDown() {
+ when (state) {
+ State.IDLE -> {
+ startPressedTimeoutWait()
+ state = State.TIMEOUT_WAIT
+ }
+ State.RUNNING_BACKWARDS -> effectAnimator.cancel()
+ else -> {}
+ }
+ }
+
+ private fun startPressedTimeoutWait() {
+ waitJob =
+ scope?.launch {
+ try {
+ delay(PRESSED_TIMEOUT)
+ handleTimeoutComplete()
+ } catch (_: CancellationException) {
+ state = State.IDLE
+ }
+ }
+ }
+
+ private fun handleActionUp() {
+ when (state) {
+ State.TIMEOUT_WAIT -> {
+ waitJob?.cancel()
+ _actionType.value = ActionType.CLICK
+ state = State.IDLE
+ }
+ State.RUNNING_FORWARD -> {
+ reverse()
+ state = State.RUNNING_BACKWARDS
+ }
+ else -> {}
+ }
+ }
+
+ private fun handleActionCancel() {
+ when (state) {
+ State.TIMEOUT_WAIT -> {
+ waitJob?.cancel()
+ state = State.IDLE
+ }
+ State.RUNNING_FORWARD -> {
+ reverse()
+ state = State.RUNNING_BACKWARDS
+ }
+ else -> {}
+ }
+ }
+
+ private fun handleAnimationStart() {
+ vibrate(longPressHint)
+ state = State.RUNNING_FORWARD
+ }
+
+ /** This function is called both when an animator completes or gets cancelled */
+ private fun handleAnimationComplete() {
+ if (state == State.RUNNING_FORWARD) {
+ vibrate(snapEffect)
+ _actionType.value = ActionType.LONG_PRESS
+ _effectProgress.value = null
+ }
+ if (state != State.TIMEOUT_WAIT) {
+ // This will happen if the animator did not finish by being cancelled
+ state = State.IDLE
+ }
+ }
+
+ private fun handleAnimationCancel() {
+ _effectProgress.value = 0f
+ startPressedTimeoutWait()
+ state = State.TIMEOUT_WAIT
+ }
+
+ private fun handleTimeoutComplete() {
+ if (state == State.TIMEOUT_WAIT && !effectAnimator.isRunning) {
+ effectAnimator.start()
+ }
+ }
+
+ fun clearActionType() {
+ _actionType.value = null
+ }
+
+ enum class State {
+ IDLE, /* The effect is idle waiting for touch input */
+ TIMEOUT_WAIT, /* The effect is waiting for a [PRESSED_TIMEOUT] period */
+ RUNNING_FORWARD, /* The effect is running normally */
+ RUNNING_BACKWARDS, /* The effect was interrupted and is now running backwards */
+ }
+
+ /* A type of action to perform on the view depending on the effect's state and logic */
+ enum class ActionType {
+ CLICK,
+ LONG_PRESS,
+ }
+
+ companion object {
+ /**
+ * A timeout to let the tile resolve if it is being swiped/scrolled. Since QS tiles are
+ * inside a scrollable container, they will be considered pressed only after a tap timeout.
+ */
+ val PRESSED_TIMEOUT = ViewConfiguration.getTapTimeout().toLong() + 20L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
new file mode 100644
index 0000000..e298154
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.haptics.qs
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.qs.tileimpl.QSTileViewImpl
+import kotlinx.coroutines.launch
+
+object QSLongPressEffectViewBinder {
+
+ fun bind(
+ tile: QSTileViewImpl,
+ effect: QSLongPressEffect?,
+ ) {
+ if (effect == null) return
+
+ tile.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ effect.scope = this
+
+ launch {
+ effect.effectProgress.collect { progress ->
+ progress?.let {
+ if (it == 0f) {
+ tile.bringToFront()
+ }
+ tile.updateLongPressEffectProperties(it)
+ }
+ }
+ }
+
+ launch {
+ effect.actionType.collect { action ->
+ action?.let {
+ when (it) {
+ QSLongPressEffect.ActionType.CLICK -> tile.performClick()
+ QSLongPressEffect.ActionType.LONG_PRESS -> tile.performLongClick()
+ }
+ effect.clearActionType()
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 106fdf1..5565ee2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -42,6 +42,7 @@
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.ComposeLockscreen
import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder
@@ -107,6 +108,7 @@
private val lockscreenContentViewModel: LockscreenContentViewModel,
private val lockscreenSceneBlueprintsLazy: Lazy<Set<LockscreenSceneBlueprint>>,
private val keyguardBlueprintViewBinder: KeyguardBlueprintViewBinder,
+ private val clockInteractor: KeyguardClockInteractor,
) : CoreStartable {
private var rootViewHandle: DisposableHandle? = null
@@ -220,7 +222,11 @@
blueprints.mapNotNull { it as? ComposableLockscreenSceneBlueprint }.toSet()
return ComposeView(context).apply {
setContent {
- LockscreenContent(viewModel = viewModel, blueprints = sceneBlueprints)
+ LockscreenContent(
+ viewModel = viewModel,
+ blueprints = sceneBlueprints,
+ clockInteractor = clockInteractor
+ )
.Content(modifier = Modifier.fillMaxSize())
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index a37397d..43a8b40 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -140,13 +140,13 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.ui.viewmodel.DreamViewModel;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.SystemPropertiesHelper;
import com.android.systemui.keyguard.dagger.KeyguardModule;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.shared.model.TransitionStep;
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -1229,9 +1229,7 @@
if (isDream) {
initAlphaForAnimationTargets(wallpapers);
- getRemoteSurfaceAlphaApplier().accept(0.0f);
- mDreamingToLockscreenTransitionViewModel.get()
- .startTransition();
+ mDreamViewModel.get().startTransitionFromDream();
mUnoccludeFromDreamFinishedCallback = finishedCallback;
return;
}
@@ -1359,8 +1357,7 @@
private final UiEventLogger mUiEventLogger;
private final SessionTracker mSessionTracker;
private final CoroutineDispatcher mMainDispatcher;
- private final Lazy<DreamingToLockscreenTransitionViewModel>
- mDreamingToLockscreenTransitionViewModel;
+ private final Lazy<DreamViewModel> mDreamViewModel;
private RemoteAnimationTarget mRemoteAnimationTarget;
private final Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
@@ -1409,7 +1406,7 @@
SystemSettings systemSettings,
SystemClock systemClock,
@Main CoroutineDispatcher mainDispatcher,
- Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
+ Lazy<DreamViewModel> dreamViewModel,
SystemPropertiesHelper systemPropertiesHelper,
Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
SelectedUserInteractor selectedUserInteractor,
@@ -1480,7 +1477,7 @@
mUiEventLogger = uiEventLogger;
mSessionTracker = sessionTracker;
- mDreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel;
+ mDreamViewModel = dreamViewModel;
mWmLockscreenVisibilityManager = wmLockscreenVisibilityManager;
mMainDispatcher = mainDispatcher;
@@ -1611,9 +1608,8 @@
ViewRootImpl viewRootImpl = mKeyguardViewControllerLazy.get().getViewRootImpl();
if (viewRootImpl != null) {
- DreamingToLockscreenTransitionViewModel viewModel =
- mDreamingToLockscreenTransitionViewModel.get();
- collectFlow(viewRootImpl.getView(), viewModel.getDreamOverlayAlpha(),
+ final DreamViewModel viewModel = mDreamViewModel.get();
+ collectFlow(viewRootImpl.getView(), viewModel.getDreamAlpha(),
getRemoteSurfaceAlphaApplier(), mMainDispatcher);
collectFlow(viewRootImpl.getView(), viewModel.getTransitionEnded(),
getFinishedCallbackConsumer(), mMainDispatcher);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 5306645..a243b8e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -43,6 +43,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.ui.viewmodel.DreamViewModel;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.SystemPropertiesHelper;
@@ -59,7 +60,6 @@
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger;
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLoggerImpl;
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransitionModule;
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.settings.UserTracker;
@@ -160,7 +160,7 @@
SystemSettings systemSettings,
SystemClock systemClock,
@Main CoroutineDispatcher mainDispatcher,
- Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
+ Lazy<DreamViewModel> dreamViewModel,
SystemPropertiesHelper systemPropertiesHelper,
Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
SelectedUserInteractor selectedUserInteractor,
@@ -207,7 +207,7 @@
systemSettings,
systemClock,
mainDispatcher,
- dreamingToLockscreenTransitionViewModel,
+ dreamViewModel,
systemPropertiesHelper,
wmLockscreenVisibilityManager,
selectedUserInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index f5f5571..882f231 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
@@ -175,7 +175,6 @@
pw.println("isFingerprintAuthCurrentlyAllowed=${isFingerprintAuthCurrentlyAllowed.value}")
pw.println("isNonStrongBiometricAllowed=${isNonStrongBiometricAllowed.value}")
pw.println("isStrongBiometricAllowed=${isStrongBiometricAllowed.value}")
- pw.println("isFingerprintEnabledByDevicePolicy=${isFingerprintEnabledByDevicePolicy.value}")
}
/** UserId of the current selected user. */
@@ -324,22 +323,14 @@
else isNonStrongBiometricAllowed
}
- private val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean> =
- selectedUserId
- .flatMapLatest { userId ->
- devicePolicyChangedForAllUsers
- .transformLatest { emit(devicePolicyManager.isFingerprintDisabled(userId)) }
- .flowOn(backgroundDispatcher)
- .distinctUntilChanged()
- }
- .stateIn(
- scope,
- started = SharingStarted.Eagerly,
- initialValue =
- devicePolicyManager.isFingerprintDisabled(
- userRepository.getSelectedUserInfo().id
- )
- )
+ private val isFingerprintEnabledByDevicePolicy: Flow<Boolean> =
+ selectedUserId.flatMapLatest { userId ->
+ devicePolicyChangedForAllUsers
+ .transformLatest { emit(devicePolicyManager.isFingerprintDisabled(userId)) }
+ .onStart { emit(devicePolicyManager.isFingerprintDisabled(userId)) }
+ .flowOn(backgroundDispatcher)
+ .distinctUntilChanged()
+ }
override val isFingerprintEnrolledAndEnabled: StateFlow<Boolean> =
isFingerprintEnrolled
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 3137138..9a6088d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -35,6 +35,7 @@
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
@@ -97,6 +98,18 @@
}
}
+ fun startToGlanceableHubTransition() {
+ scope.launch {
+ KeyguardWmStateRefactor.isUnexpectedlyInLegacyMode()
+ if (
+ transitionInteractor.startedKeyguardState.replayCache.last() ==
+ KeyguardState.DREAMING
+ ) {
+ startTransitionTo(KeyguardState.GLANCEABLE_HUB)
+ }
+ }
+ }
+
private fun listenForDreamingToOccluded() {
if (KeyguardWmStateRefactor.isEnabled) {
scope.launch {
@@ -205,14 +218,18 @@
return ValueAnimator().apply {
interpolator = Interpolators.LINEAR
duration =
- if (toState == KeyguardState.LOCKSCREEN) TO_LOCKSCREEN_DURATION.inWholeMilliseconds
- else DEFAULT_DURATION.inWholeMilliseconds
+ when (toState) {
+ KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
+ KeyguardState.GLANCEABLE_HUB -> TO_GLANCEABLE_HUB_DURATION
+ else -> DEFAULT_DURATION
+ }.inWholeMilliseconds
}
}
companion object {
const val TAG = "FromDreamingTransitionInteractor"
private val DEFAULT_DURATION = 500.milliseconds
+ val TO_GLANCEABLE_HUB_DURATION = 1.seconds
val TO_LOCKSCREEN_DURATION = 1167.milliseconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
index 7443010..197221a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
@@ -21,7 +21,6 @@
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalTransitionProgress
import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
@@ -29,13 +28,10 @@
import com.android.systemui.util.kotlin.sample
import java.util.UUID
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.flow.flowOn
class GlanceableHubTransitions
@Inject
constructor(
- @Background private val bgDispatcher: CoroutineDispatcher,
private val transitionInteractor: KeyguardTransitionInteractor,
private val transitionRepository: KeyguardTransitionRepository,
private val communalInteractor: CommunalInteractor,
@@ -64,16 +60,16 @@
communalInteractor
.transitionProgressToScene(toScene)
.sample(
- transitionInteractor.startedKeyguardTransitionStep.flowOn(bgDispatcher),
+ transitionInteractor.startedKeyguardState,
::Pair,
)
- .collect { (transitionProgress, lastStartedStep) ->
+ .collect { (transitionProgress, lastStartedState) ->
val id = transitionId
if (id == null) {
// No transition started.
if (
transitionProgress is CommunalTransitionProgress.Transition &&
- lastStartedStep.to == fromState
+ lastStartedState == fromState
) {
transitionId =
transitionRepository.startTransition(
@@ -86,7 +82,7 @@
)
}
} else {
- if (lastStartedStep.to != toState) {
+ if (lastStartedState != toState) {
return@collect
}
// An existing `id` means a transition is started, and calls to
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TrustInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TrustInteractor.kt
new file mode 100644
index 0000000..2ff6e16
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TrustInteractor.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.TrustRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+
+/** Encapsulates any state relevant to trust agents and trust grants. */
+@SysUISingleton
+class TrustInteractor @Inject constructor(repository: TrustRepository) {
+ /**
+ * Whether the current user has a trust agent enabled. This is true if the user has at least one
+ * trust agent enabled in settings.
+ */
+ val isEnrolledAndEnabled: StateFlow<Boolean> = repository.isCurrentUserTrustUsuallyManaged
+
+ /**
+ * Whether the current user's trust agent is currently allowed, this will be false if trust
+ * agent is disabled for any reason (security timeout, disabled on lock screen by opening the
+ * power menu, etc), it does not include temporary biometric lockouts.
+ */
+ val isTrustAgentCurrentlyAllowed: StateFlow<Boolean> = repository.isCurrentUserTrustManaged
+
+ /** Whether the current user is trusted by any of the enabled trust agents. */
+ val isTrusted: Flow<Boolean> = repository.isCurrentUserTrusted
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt
index 08904b6..d6f3634 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt
@@ -32,6 +32,9 @@
val isPrimaryAuthRequiredAfterTimeout =
containsFlag(flag, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT)
+ val isPrimaryAuthRequiredAfterLockout =
+ containsFlag(flag, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT)
+
val isPrimaryAuthRequiredAfterDpmLockdown =
containsFlag(
flag,
@@ -47,7 +50,7 @@
LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED
)
- val primaryAuthRequiredForUnattendedUpdate =
+ val isPrimaryAuthRequiredForUnattendedUpdate =
containsFlag(
flag,
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
index c64f277..789e4fb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
@@ -19,6 +19,7 @@
import com.android.app.animation.Interpolators.EMPHASIZED
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.res.R
@@ -36,7 +37,9 @@
constructor(
animationFlow: KeyguardTransitionAnimationFlow,
configurationInteractor: ConfigurationInteractor,
+ private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor,
) {
+ fun startTransition() = fromDreamingTransitionInteractor.startToGlanceableHubTransition()
private val transitionAnimation =
animationFlow.setup(
@@ -58,6 +61,9 @@
)
}
+ // Keep the dream visible while the hub swipes in over the dream.
+ val dreamAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(1f)
+
val dreamOverlayAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
duration = 167.milliseconds,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
index d3277cd..f191aa7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -20,16 +20,13 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
/**
* Breaks down DREAMING->LOCKSCREEN transition into discrete steps for corresponding views to
@@ -40,7 +37,6 @@
class DreamingToLockscreenTransitionViewModel
@Inject
constructor(
- keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
) : DeviceEntryIconTransition {
@@ -53,12 +49,6 @@
to = KeyguardState.LOCKSCREEN,
)
- val transitionEnded =
- keyguardTransitionInteractor.fromDreamingTransition.filter { step ->
- step.transitionState == TransitionState.FINISHED ||
- step.transitionState == TransitionState.CANCELED
- }
-
/** Dream overlay y-translation on exit */
fun dreamOverlayTranslationY(translatePx: Int): Flow<Float> {
return transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
index e5b5964..abf2372 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
@@ -79,6 +79,8 @@
val notificationAlpha: Flow<Float> = keyguardAlpha
+ val shortcutsAlpha: Flow<Float> = keyguardAlpha
+
val notificationTranslationX: Flow<Float> =
keyguardTranslationX.map { it.value }.filterNotNull()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index f981fd5..58c45c7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -36,6 +36,7 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@SysUISingleton
@@ -67,7 +68,16 @@
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = LARGE
+ initialValue = LARGE,
+ )
+
+ val isLargeClockVisible =
+ clockSize
+ .map { it == LARGE }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
)
val currentClock = keyguardClockInteractor.currentClock
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
index 4db942cc..c4383fc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
@@ -52,6 +52,7 @@
occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
offToLockscreenTransitionViewModel: OffToLockscreenTransitionViewModel,
primaryBouncerToLockscreenTransitionViewModel: PrimaryBouncerToLockscreenTransitionViewModel,
+ glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel,
lockscreenToDozingTransitionViewModel: LockscreenToDozingTransitionViewModel,
lockscreenToDreamingHostedTransitionViewModel: LockscreenToDreamingHostedTransitionViewModel,
@@ -59,6 +60,7 @@
lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel,
lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel,
lockscreenToPrimaryBouncerTransitionViewModel: LockscreenToPrimaryBouncerTransitionViewModel,
+ lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
transitionInteractor: KeyguardTransitionInteractor,
) {
@@ -110,6 +112,7 @@
occludedToLockscreenTransitionViewModel.shortcutsAlpha,
offToLockscreenTransitionViewModel.shortcutsAlpha,
primaryBouncerToLockscreenTransitionViewModel.shortcutsAlpha,
+ glanceableHubToLockscreenTransitionViewModel.shortcutsAlpha,
)
/** alpha while fading the quick affordances in */
@@ -122,6 +125,7 @@
lockscreenToGoneTransitionViewModel.shortcutsAlpha,
lockscreenToOccludedTransitionViewModel.shortcutsAlpha,
lockscreenToPrimaryBouncerTransitionViewModel.shortcutsAlpha,
+ lockscreenToGlanceableHubTransitionViewModel.shortcutsAlpha,
shadeExpansionAlpha,
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index d89523d..993e81b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -14,21 +14,29 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
/** Models UI state and handles user input for the lockscreen scene. */
@@ -43,37 +51,69 @@
val longPress: KeyguardLongPressViewModel,
val notifications: NotificationsPlaceholderViewModel,
) {
- /** The key of the scene we should switch to when swiping up. */
- val upDestinationSceneKey: StateFlow<SceneKey> =
- deviceEntryInteractor.isUnlocked
- .map { isUnlocked -> upDestinationSceneKey(isUnlocked) }
+ val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ combine(
+ deviceEntryInteractor.isUnlocked,
+ communalInteractor.isCommunalAvailable,
+ shadeInteractor.shadeMode,
+ ) { isDeviceUnlocked, isCommunalAvailable, shadeMode ->
+ destinationScenes(
+ isDeviceUnlocked = isDeviceUnlocked,
+ isCommunalAvailable = isCommunalAvailable,
+ shadeMode = shadeMode,
+ )
+ }
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = upDestinationSceneKey(deviceEntryInteractor.isUnlocked.value),
+ initialValue =
+ destinationScenes(
+ isDeviceUnlocked = deviceEntryInteractor.isUnlocked.value,
+ isCommunalAvailable = false,
+ shadeMode = shadeInteractor.shadeMode.value,
+ ),
)
- private fun upDestinationSceneKey(isUnlocked: Boolean): SceneKey {
- return if (isUnlocked) Scenes.Gone else Scenes.Bouncer
+ private fun destinationScenes(
+ isDeviceUnlocked: Boolean,
+ isCommunalAvailable: Boolean,
+ shadeMode: ShadeMode,
+ ): Map<UserAction, UserActionResult> {
+ val quickSettingsIfSingleShade =
+ if (shadeMode is ShadeMode.Single) {
+ Scenes.QuickSettings
+ } else {
+ Scenes.Shade
+ }
+
+ return mapOf(
+ Swipe.Left to UserActionResult(Scenes.Communal).takeIf { isCommunalAvailable },
+ Swipe.Up to if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer,
+
+ // Swiping down from the top edge goes to QS (or shade if in split shade mode).
+ swipeDownFromTop(pointerCount = 1) to quickSettingsIfSingleShade,
+ swipeDownFromTop(pointerCount = 2) to quickSettingsIfSingleShade,
+
+ // Swiping down, not from the edge, always navigates to the shade scene.
+ swipeDown(pointerCount = 1) to Scenes.Shade,
+ swipeDown(pointerCount = 2) to Scenes.Shade,
+ )
+ .filterValues { it != null }
+ .mapValues { checkNotNull(it.value) }
}
- /** The key of the scene we should switch to when swiping left. */
- val leftDestinationSceneKey: StateFlow<SceneKey?> =
- communalInteractor.isCommunalAvailable
- .map { available -> if (available) Scenes.Communal else null }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = null,
- )
+ private fun swipeDownFromTop(pointerCount: Int): Swipe {
+ return Swipe(
+ SwipeDirection.Down,
+ fromSource = Edge.Top,
+ pointerCount = pointerCount,
+ )
+ }
- /** The key of the scene we should switch to when swiping down from the top edge. */
- val downFromTopEdgeDestinationSceneKey: StateFlow<SceneKey?> =
- shadeInteractor.isSplitShade
- .map { isSplitShade -> Scenes.QuickSettings.takeUnless { isSplitShade } }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = null,
- )
+ private fun swipeDown(pointerCount: Int): Swipe {
+ return Swipe(
+ SwipeDirection.Down,
+ pointerCount = pointerCount,
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
index 978e71e..b7f7b06 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
@@ -81,6 +81,8 @@
val notificationAlpha: Flow<Float> = keyguardAlpha
+ val shortcutsAlpha: Flow<Float> = keyguardAlpha
+
val notificationTranslationX: Flow<Float> =
keyguardTranslationX.map { it.value }.filterNotNull()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
index 027a739..bf22563 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
@@ -19,6 +19,7 @@
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import javax.inject.Inject
-class MediaCarouselViewModel @Inject constructor(mediaDataManager: MediaDataManager) {
- val isMediaVisible: Boolean = mediaDataManager.hasActiveMediaOrRecommendation()
+class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
+ val isMediaVisible: Boolean
+ get() = mediaDataManager.hasActiveMediaOrRecommendation()
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index f4903f1..768bb8e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -131,6 +131,7 @@
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.rotation.RotationButton;
@@ -199,6 +200,7 @@
private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
private final KeyguardStateController mKeyguardStateController;
private final ShadeViewController mShadeViewController;
+ private final PanelExpansionInteractor mPanelExpansionInteractor;
private final NotificationRemoteInputManager mNotificationRemoteInputManager;
private final OverviewProxyService mOverviewProxyService;
private final NavigationModeController mNavigationModeController;
@@ -537,6 +539,7 @@
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
KeyguardStateController keyguardStateController,
ShadeViewController shadeViewController,
+ PanelExpansionInteractor panelExpansionInteractor,
NotificationRemoteInputManager notificationRemoteInputManager,
NotificationShadeDepthController notificationShadeDepthController,
@Main Handler mainHandler,
@@ -575,6 +578,7 @@
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
mKeyguardStateController = keyguardStateController;
mShadeViewController = shadeViewController;
+ mPanelExpansionInteractor = panelExpansionInteractor;
mNotificationRemoteInputManager = notificationRemoteInputManager;
mOverviewProxyService = overviewProxyService;
mNavigationModeController = navigationModeController;
@@ -749,7 +753,7 @@
final Display display = mView.getDisplay();
mView.setComponents(mRecentsOptional);
if (mCentralSurfacesOptionalLazy.get().isPresent()) {
- mView.setComponents(mShadeViewController);
+ mView.setComponents(mShadeViewController, mPanelExpansionInteractor);
}
mView.setDisabledFlags(mDisabledFlags1, mSysUiFlagsContainer);
mView.setOnVerticalChangedListener(this::onVerticalChanged);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index c5190a2..1927f49 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -75,6 +75,7 @@
import com.android.systemui.res.R;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.shared.rotation.FloatingRotationButton;
import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdatesCallback;
import com.android.systemui.shared.rotation.RotationButtonController;
@@ -149,7 +150,9 @@
private NavigationBarInflaterView mNavigationInflaterView;
private Optional<Recents> mRecentsOptional = Optional.empty();
@Nullable
- private ShadeViewController mPanelView;
+ private ShadeViewController mShadeViewController;
+ @Nullable
+ private PanelExpansionInteractor mPanelExpansionInteractor;
private RotationContextButton mRotationContextButton;
private FloatingRotationButton mFloatingRotationButton;
private RotationButtonController mRotationButtonController;
@@ -347,8 +350,9 @@
}
/** */
- public void setComponents(ShadeViewController panel) {
- mPanelView = panel;
+ public void setComponents(ShadeViewController svc, PanelExpansionInteractor pei) {
+ mShadeViewController = svc;
+ mPanelExpansionInteractor = pei;
updatePanelSystemUiStateFlags();
}
@@ -750,10 +754,10 @@
private void updatePanelSystemUiStateFlags() {
if (SysUiState.DEBUG) {
- Log.d(TAG, "Updating panel sysui state flags: panelView=" + mPanelView);
+ Log.d(TAG, "Updating panel sysui state flags: panelView=" + mShadeViewController);
}
- if (mPanelView != null) {
- mPanelView.updateSystemUiStateFlags();
+ if (mShadeViewController != null) {
+ mShadeViewController.updateSystemUiStateFlags();
}
}
@@ -801,7 +805,8 @@
*/
void updateSlippery() {
setSlippery(!isQuickStepSwipeUpEnabled() ||
- (mPanelView != null && mPanelView.isFullyExpanded() && !mPanelView.isCollapsing()));
+ (mPanelExpansionInteractor != null && mPanelExpansionInteractor.isFullyExpanded()
+ && !mPanelExpansionInteractor.isCollapsing()));
}
void setSlippery(boolean slippery) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 91c86df..9d0ea5e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -19,6 +19,7 @@
import static android.view.InputDevice.SOURCE_TOUCHPAD;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
+import static com.android.systemui.Flags.edgebackGestureHandlerGetRunningTasksBackground;
import static com.android.systemui.classifier.Classifier.BACK_GESTURE;
import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll;
import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadThreeFingerSwipe;
@@ -54,7 +55,6 @@
import android.view.IWindowManager;
import android.view.InputDevice;
import android.view.InputEvent;
-import android.view.InputMonitor;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -104,6 +104,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import javax.inject.Inject;
@@ -151,7 +152,12 @@
private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
@Override
public void onTaskStackChanged() {
- mGestureBlockingActivityRunning = isGestureBlockingActivityRunning();
+ if (edgebackGestureHandlerGetRunningTasksBackground()) {
+ mBackgroundExecutor.execute(() -> mGestureBlockingActivityRunning.set(
+ isGestureBlockingActivityRunning()));
+ } else {
+ mGestureBlockingActivityRunning.set(isGestureBlockingActivityRunning());
+ }
}
@Override
public void onTaskCreated(int taskId, ComponentName componentName) {
@@ -241,6 +247,8 @@
private final PointF mDownPoint = new PointF();
private final PointF mEndPoint = new PointF();
+ private AtomicBoolean mGestureBlockingActivityRunning = new AtomicBoolean();
+
private boolean mThresholdCrossed = false;
private boolean mAllowGesture = false;
private boolean mLogGesture = false;
@@ -256,7 +264,6 @@
private boolean mIsEnabled;
private boolean mIsNavBarShownTransiently;
private boolean mIsBackGestureAllowed;
- private boolean mGestureBlockingActivityRunning;
private boolean mIsNewBackAffordanceEnabled;
private boolean mIsTrackpadGestureFeaturesEnabled;
private boolean mIsTrackpadThreeFingerSwipe;
@@ -1017,7 +1024,7 @@
mInRejectedExclusion = false;
boolean isWithinInsets = isWithinInsets((int) ev.getX(), (int) ev.getY());
boolean isBackAllowedCommon = !mDisabledForQuickstep && mIsBackGestureAllowed
- && !mGestureBlockingActivityRunning
+ && !mGestureBlockingActivityRunning.get()
&& !QuickStepContract.isBackGestureDisabled(mSysUiFlags,
mIsTrackpadThreeFingerSwipe)
&& !isTrackpadScroll(mIsTrackpadGestureFeaturesEnabled, ev);
@@ -1053,8 +1060,8 @@
curTime, curTimeStr, mAllowGesture, mIsTrackpadThreeFingerSwipe,
mIsOnLeftEdge, mDeferSetIsOnLeftEdge, mIsBackGestureAllowed,
QuickStepContract.isBackGestureDisabled(mSysUiFlags,
- mIsTrackpadThreeFingerSwipe),
- mDisabledForQuickstep, mGestureBlockingActivityRunning, mIsInPip, mDisplaySize,
+ mIsTrackpadThreeFingerSwipe), mDisabledForQuickstep,
+ mGestureBlockingActivityRunning.get(), mIsInPip, mDisplaySize,
mEdgeWidthLeft, mLeftInset, mEdgeWidthRight, mRightInset, mExcludeRegion));
} else if (mAllowGesture || mLogGesture) {
if (!mThresholdCrossed) {
@@ -1236,7 +1243,7 @@
pw.println(" mIsBackGestureAllowed=" + mIsBackGestureAllowed);
pw.println(" mIsGestureHandlingEnabled=" + mIsGestureHandlingEnabled);
pw.println(" mIsNavBarShownTransiently=" + mIsNavBarShownTransiently);
- pw.println(" mGestureBlockingActivityRunning=" + mGestureBlockingActivityRunning);
+ pw.println(" mGestureBlockingActivityRunning=" + mGestureBlockingActivityRunning.get());
pw.println(" mAllowGesture=" + mAllowGesture);
pw.println(" mUseMLModel=" + mUseMLModel);
pw.println(" mDisabledForQuickstep=" + mDisabledForQuickstep);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 2440651..cd65119 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -38,6 +38,7 @@
import com.android.systemui.settings.brightness.BrightnessController;
import com.android.systemui.settings.brightness.BrightnessMirrorHandler;
import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
@@ -90,9 +91,11 @@
FalsingManager falsingManager,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
SplitShadeStateController splitShadeStateController,
- SceneContainerFlags sceneContainerFlags) {
+ SceneContainerFlags sceneContainerFlags,
+ VibratorHelper vibratorHelper) {
super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost,
- metricsLogger, uiEventLogger, qsLogger, dumpManager, splitShadeStateController);
+ metricsLogger, uiEventLogger, qsLogger, dumpManager, splitShadeStateController,
+ vibratorHelper);
mTunerService = tunerService;
mQsCustomizerController = qsCustomizerController;
mQsTileRevealControllerFactory = qsTileRevealControllerFactory;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 975c871..5e12b9d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -39,6 +39,7 @@
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileViewImpl;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.animation.DisappearParameters;
@@ -87,6 +88,8 @@
private SplitShadeStateController mSplitShadeStateController;
+ private final VibratorHelper mVibratorHelper;
+
@VisibleForTesting
protected final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
new QSPanel.OnConfigurationChangedListener() {
@@ -144,7 +147,8 @@
UiEventLogger uiEventLogger,
QSLogger qsLogger,
DumpManager dumpManager,
- SplitShadeStateController splitShadeStateController
+ SplitShadeStateController splitShadeStateController,
+ VibratorHelper vibratorHelper
) {
super(view);
mHost = host;
@@ -158,6 +162,7 @@
mSplitShadeStateController = splitShadeStateController;
mShouldUseSplitNotificationShade =
mSplitShadeStateController.shouldUseSplitNotificationShade(getResources());
+ mVibratorHelper = vibratorHelper;
}
@Override
@@ -300,7 +305,8 @@
}
private void addTile(final QSTile tile, boolean collapsedView) {
- final QSTileViewImpl tileView = new QSTileViewImpl(getContext(), collapsedView);
+ final QSTileViewImpl tileView = new QSTileViewImpl(
+ getContext(), collapsedView, mVibratorHelper);
final TileRecord r = new TileRecord(tile, tileView);
// TODO(b/250618218): Remove the QSLogger in QSTileViewImpl once we know the root cause of
// b/250618218.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index a8e88da..05bb088 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -32,6 +32,7 @@
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.util.leak.RotationUtils;
@@ -56,10 +57,11 @@
@Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA)
Provider<Boolean> usingCollapsedLandscapeMediaProvider,
MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
- DumpManager dumpManager, SplitShadeStateController splitShadeStateController
+ DumpManager dumpManager, SplitShadeStateController splitShadeStateController,
+ VibratorHelper vibratorHelper
) {
super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
- uiEventLogger, qsLogger, dumpManager, splitShadeStateController);
+ uiEventLogger, qsLogger, dumpManager, splitShadeStateController, vibratorHelper);
mUsingCollapsedLandscapeMediaProvider = usingCollapsedLandscapeMediaProvider;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.kt
new file mode 100644
index 0000000..a2ded6a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.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.qs.tileimpl
+
+/**
+ * List of properties that define the state of a tile during a long-press gesture.
+ *
+ * These properties are used during animation if a tile supports a long-press action.
+ */
+data class QSLongPressProperties(
+ var xScale: Float,
+ var yScale: Float,
+ var cornerRadius: Float,
+ var backgroundColor: Int,
+ var labelColor: Int,
+ var secondaryLabelColor: Int,
+ var chevronColor: Int,
+ var overlayColor: Int,
+ var iconColor: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 35cac4b..1456747 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -587,7 +587,7 @@
name = "handleClick";
if (mState.disabledByPolicy) {
Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
- mContext, mEnforcedAdmin);
+ mEnforcedAdmin);
mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
} else {
mQSLogger.logHandleClick(mTileSpec, msg.arg1);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 6cc682a..63963de 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -26,6 +26,7 @@
import android.graphics.Color
import android.graphics.PorterDuff
import android.graphics.drawable.Drawable
+import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.LayerDrawable
import android.graphics.drawable.RippleDrawable
import android.os.Trace
@@ -36,6 +37,7 @@
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
+import android.view.ViewConfiguration
import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
@@ -48,9 +50,12 @@
import com.android.app.tracing.traceSection
import com.android.settingslib.Utils
import com.android.systemui.Flags
+import com.android.systemui.Flags.quickSettingsVisualHapticsLongpress
import com.android.systemui.FontSizeUtils
import com.android.systemui.animation.LaunchableView
import com.android.systemui.animation.LaunchableViewDelegate
+import com.android.systemui.haptics.qs.QSLongPressEffect
+import com.android.systemui.haptics.qs.QSLongPressEffectViewBinder
import com.android.systemui.plugins.qs.QSIconView
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.qs.QSTile.AdapterState
@@ -58,12 +63,15 @@
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH
import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.children
import java.util.Objects
private const val TAG = "QSTileViewImpl"
open class QSTileViewImpl @JvmOverloads constructor(
context: Context,
- private val collapsed: Boolean = false
+ private val collapsed: Boolean = false,
+ private val vibratorHelper: VibratorHelper? = null,
) : QSTileView(context), HeightOverrideable, LaunchableView {
companion object {
@@ -163,6 +171,7 @@
private var lastStateDescription: CharSequence? = null
private var tileState = false
private var lastState = INVALID
+ private var lastIconTint = 0
private val launchableViewDelegate = LaunchableViewDelegate(
this,
superSetVisibility = { super.setVisibility(it) },
@@ -171,6 +180,12 @@
private val locInScreen = IntArray(2)
+ /** Visuo-haptic long-press effects */
+ private var longPressEffect: QSLongPressEffect? = null
+ private var initialLongPressProperties: QSLongPressProperties? = null
+ private var finalLongPressProperties: QSLongPressProperties? = null
+ private val colorEvaluator = ArgbEvaluator.getInstance()
+
init {
val typedValue = TypedValue()
if (!getContext().theme.resolveAttribute(R.attr.isQsTheme, typedValue, true)) {
@@ -339,6 +354,9 @@
true
}
)
+ if (quickSettingsVisualHapticsLongpress()) {
+ isHapticFeedbackEnabled = false // Haptics will be handled by the [QSLongPressEffect]
+ }
}
private fun init(
@@ -589,6 +607,27 @@
lastState = state.state
lastDisabledByPolicy = state.disabledByPolicy
+ lastIconTint = icon.getColor(state)
+
+ // Long-press effects
+ if (quickSettingsVisualHapticsLongpress()){
+ if (state.handlesLongClick) {
+ // initialize the long-press effect and set it as the touch listener
+ showRippleEffect = false
+ initializeLongPressEffect()
+ setOnTouchListener(longPressEffect)
+ QSLongPressEffectViewBinder.bind(this, longPressEffect)
+ } else {
+ // Long-press effects might have been enabled before but the new state does not
+ // handle a long-press. In this case, we go back to the behaviour of a regular tile
+ // and clean-up the resources
+ showRippleEffect = isClickable
+ setOnTouchListener(null)
+ longPressEffect = null
+ initialLongPressProperties = null
+ finalLongPressProperties = null
+ }
+ }
}
private fun setAllColors(
@@ -709,6 +748,140 @@
}
}
+ override fun onActivityLaunchAnimationEnd() = resetLongPressEffectProperties()
+
+ fun updateLongPressEffectProperties(effectProgress: Float) {
+ if (!isLongClickable) return
+ setAllColors(
+ colorEvaluator.evaluate(
+ effectProgress,
+ initialLongPressProperties?.backgroundColor ?: 0,
+ finalLongPressProperties?.backgroundColor ?: 0,
+ ) as Int,
+ colorEvaluator.evaluate(
+ effectProgress,
+ initialLongPressProperties?.labelColor ?: 0,
+ finalLongPressProperties?.labelColor ?: 0,
+ ) as Int,
+ colorEvaluator.evaluate(
+ effectProgress,
+ initialLongPressProperties?.secondaryLabelColor ?: 0,
+ finalLongPressProperties?.secondaryLabelColor ?: 0,
+ ) as Int,
+ colorEvaluator.evaluate(
+ effectProgress,
+ initialLongPressProperties?.chevronColor ?: 0,
+ finalLongPressProperties?.chevronColor ?: 0,
+ ) as Int,
+ colorEvaluator.evaluate(
+ effectProgress,
+ initialLongPressProperties?.overlayColor ?: 0,
+ finalLongPressProperties?.overlayColor ?: 0,
+ ) as Int,
+ )
+ icon.setTint(
+ icon.mIcon as ImageView,
+ colorEvaluator.evaluate(
+ effectProgress,
+ initialLongPressProperties?.iconColor ?: 0,
+ finalLongPressProperties?.iconColor ?: 0,
+ ) as Int,
+ )
+
+ val newScaleX =
+ interpolateFloat(
+ effectProgress,
+ initialLongPressProperties?.xScale ?: 1f,
+ finalLongPressProperties?.xScale ?: 1f,
+ )
+ val newScaleY =
+ interpolateFloat(
+ effectProgress,
+ initialLongPressProperties?.xScale ?: 1f,
+ finalLongPressProperties?.xScale ?: 1f,
+ )
+ val newRadius =
+ interpolateFloat(
+ effectProgress,
+ initialLongPressProperties?.cornerRadius ?: 0f,
+ finalLongPressProperties?.cornerRadius ?: 0f,
+ )
+ scaleX = newScaleX
+ scaleY = newScaleY
+ for (child in children) {
+ child.scaleX = 1f / newScaleX
+ child.scaleY = 1f / newScaleY
+ }
+ changeCornerRadius(newRadius)
+ }
+
+ private fun interpolateFloat(fraction: Float, start: Float, end: Float): Float =
+ start + fraction * (end - start)
+
+ private fun resetLongPressEffectProperties() {
+ scaleY = 1f
+ scaleX = 1f
+ for (child in children) {
+ child.scaleY = 1f
+ child.scaleX = 1f
+ }
+ changeCornerRadius(resources.getDimensionPixelSize(R.dimen.qs_corner_radius).toFloat())
+ setAllColors(
+ getBackgroundColorForState(lastState, lastDisabledByPolicy),
+ getLabelColorForState(lastState, lastDisabledByPolicy),
+ getSecondaryLabelColorForState(lastState, lastDisabledByPolicy),
+ getChevronColorForState(lastState, lastDisabledByPolicy),
+ getOverlayColorForState(lastState),
+ )
+ icon.setTint(icon.mIcon as ImageView, lastIconTint)
+ }
+
+ private fun initializeLongPressEffect() {
+ initializeLongPressProperties()
+ longPressEffect =
+ QSLongPressEffect(
+ vibratorHelper,
+ ViewConfiguration.getLongPressTimeout() - ViewConfiguration.getTapTimeout(),
+ )
+ }
+
+ private fun initializeLongPressProperties() {
+ initialLongPressProperties =
+ QSLongPressProperties(
+ /* xScale= */1f,
+ /* yScale= */1f,
+ resources.getDimensionPixelSize(R.dimen.qs_corner_radius).toFloat(),
+ getBackgroundColorForState(lastState),
+ getLabelColorForState(lastState),
+ getSecondaryLabelColorForState(lastState),
+ getChevronColorForState(lastState),
+ getOverlayColorForState(lastState),
+ lastIconTint,
+ )
+
+ finalLongPressProperties =
+ QSLongPressProperties(
+ /* xScale= */1.1f,
+ /* yScale= */1.2f,
+ resources.getDimensionPixelSize(R.dimen.qs_corner_radius).toFloat() - 20,
+ getBackgroundColorForState(Tile.STATE_ACTIVE),
+ getLabelColorForState(Tile.STATE_ACTIVE),
+ getSecondaryLabelColorForState(Tile.STATE_ACTIVE),
+ getChevronColorForState(Tile.STATE_ACTIVE),
+ getOverlayColorForState(Tile.STATE_ACTIVE),
+ Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive),
+ )
+ }
+
+ private fun changeCornerRadius(radius: Float) {
+ for (i in 0 until backgroundDrawable.numberOfLayers) {
+ val layer = backgroundDrawable.getDrawable(i)
+ if (layer is GradientDrawable) {
+ layer.cornerRadius = radius
+ }
+ }
+ }
+
@VisibleForTesting
internal fun getCurrentColors(): List<Int> = listOf(
backgroundColor,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index 32deb30..6b654be 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -34,11 +34,11 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.drawable.CircleFramedDrawable;
-import com.android.systemui.res.R;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.qs.PseudoGridView;
import com.android.systemui.qs.QSUserSwitcherEvent;
import com.android.systemui.qs.user.UserSwitchDialogController;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter;
import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -186,7 +186,7 @@
(UserRecord) view.getTag();
if (userRecord.isDisabledByAdmin()) {
final Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
- mContext, userRecord.enforcedAdmin);
+ userRecord.enforcedAdmin);
mController.startActivity(intent);
} else if (userRecord.isSwitchToEnabled) {
MetricsLogger.action(mContext, MetricsEvent.QS_SWITCH_USER);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt
index d1f8945..87b89ea 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt
@@ -96,10 +96,7 @@
is PolicyResult.TileEnabled -> false
is PolicyResult.TileDisabled -> {
val intent =
- RestrictedLockUtils.getShowAdminSupportDetailsIntent(
- context,
- policyResult.admin
- )
+ RestrictedLockUtils.getShowAdminSupportDetailsIntent(policyResult.admin)
activityStarter.postStartActivityDismissingKeyguard(intent, 0)
true
}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index 55d7f8e..8a84496 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -19,10 +19,13 @@
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
+import android.content.pm.LauncherApps
import android.content.res.Resources
import android.net.Uri
import android.os.Handler
import android.os.UserHandle
+import android.util.Log
+import androidx.core.content.FileProvider
import com.android.internal.logging.UiEventLogger
import com.android.systemui.dagger.qualifiers.LongRunning
import com.android.systemui.dagger.qualifiers.Main
@@ -34,7 +37,12 @@
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
import com.android.traceur.FileSender
import com.android.traceur.TraceUtils
+import java.io.File
+import java.io.FileOutputStream
+import java.nio.file.Files
import java.util.concurrent.Executor
+import java.util.zip.ZipEntry
+import java.util.zip.ZipOutputStream
import javax.inject.Inject
class IssueRecordingService
@@ -86,6 +94,10 @@
}
ACTION_STOP,
ACTION_STOP_NOTIF -> {
+ // ViewCapture needs to save it's data before it is disabled, or else the data will
+ // be lost. This is expected to change in the near future, and when that happens
+ // this line should be removed.
+ getSystemService(LauncherApps::class.java)?.saveViewCaptureData()
TraceUtils.traceStop(contentResolver)
}
ACTION_SHARE -> {
@@ -102,21 +114,22 @@
}
private fun shareRecording(intent: Intent) {
- val files = TraceUtils.traceDump(contentResolver, TRACE_FILE_NAME).get()
- val traceUris: MutableList<Uri> = FileSender.getUriForFiles(this, files, AUTHORITY)
-
- if (
- intent.hasExtra(EXTRA_PATH) && intent.getStringExtra(EXTRA_PATH)?.isNotEmpty() == true
- ) {
- traceUris.add(Uri.parse(intent.getStringExtra(EXTRA_PATH)))
- }
-
+ val sharableUri: Uri =
+ zipAndPackageRecordings(
+ TraceUtils.traceDump(contentResolver, TRACE_FILE_NAME).get(),
+ intent.getStringExtra(EXTRA_PATH)
+ )
+ ?: return
val sendIntent =
- FileSender.buildSendIntent(this, traceUris).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ FileSender.buildSendIntent(this, listOf(sharableUri))
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
if (mNotificationId != NOTIF_BASE_ID) {
- val currentUserId = mUserContextTracker.userContext.userId
- mNotificationManager.cancelAsUser(null, mNotificationId, UserHandle(currentUserId))
+ mNotificationManager.cancelAsUser(
+ null,
+ mNotificationId,
+ UserHandle(mUserContextTracker.userContext.userId)
+ )
}
// TODO: Debug why the notification shade isn't closing upon starting the BetterBug activity
@@ -130,11 +143,39 @@
)
}
+ private fun zipAndPackageRecordings(traceFiles: List<File>, screenRecordingUri: String?): Uri? {
+ try {
+ externalCacheDir?.mkdirs()
+ val outZip: File = File.createTempFile(TEMP_FILE_PREFIX, ZIP_SUFFIX, externalCacheDir)
+ ZipOutputStream(FileOutputStream(outZip)).use { os ->
+ traceFiles.forEach { file ->
+ os.putNextEntry(ZipEntry(file.name))
+ Files.copy(file.toPath(), os)
+ os.closeEntry()
+ }
+ if (screenRecordingUri != null) {
+ contentResolver.openInputStream(Uri.parse(screenRecordingUri))?.use {
+ os.putNextEntry(ZipEntry(SCREEN_RECORDING_ZIP_LABEL))
+ it.transferTo(os)
+ os.closeEntry()
+ }
+ }
+ }
+ return FileProvider.getUriForFile(this, AUTHORITY, outZip)
+ } catch (e: Exception) {
+ Log.e(TAG, "Failed to zip and package Recordings. Cannot share with BetterBug.", e)
+ return null
+ }
+ }
+
companion object {
private const val TAG = "IssueRecordingService"
private const val CHANNEL_ID = "issue_record"
private const val EXTRA_SCREEN_RECORD = "extra_screenRecord"
private const val EXTRA_WINSCOPE_TRACING = "extra_winscopeTracing"
+ private const val ZIP_SUFFIX = ".zip"
+ private const val TEMP_FILE_PREFIX = "issue_recording"
+ private const val SCREEN_RECORDING_ZIP_LABEL = "screen-recording.mp4"
private val DEFAULT_TRACE_TAGS = listOf<String>()
private const val DEFAULT_BUFFER_SIZE = 16384
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
index a490fe2..451fd67 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
@@ -25,6 +25,7 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
@@ -40,17 +41,17 @@
shadeInteractor: ShadeInteractor,
) {
val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
- shadeInteractor.isSplitShade
- .map { isSplitShade -> destinationScenes(isSplitShade = isSplitShade) }
+ shadeInteractor.shadeMode
+ .map { shadeMode -> destinationScenes(shadeMode = shadeMode) }
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = destinationScenes(isSplitShade = shadeInteractor.isSplitShade.value)
+ initialValue = destinationScenes(shadeMode = shadeInteractor.shadeMode.value)
)
- private fun destinationScenes(isSplitShade: Boolean): Map<UserAction, UserActionResult> {
+ private fun destinationScenes(shadeMode: ShadeMode): Map<UserAction, UserActionResult> {
return buildMap {
- if (!isSplitShade) {
+ if (shadeMode == ShadeMode.Single) {
this[
Swipe(
pointerCount = 2,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
index d8c3850..1f6d212 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
@@ -21,9 +21,8 @@
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Rect
-import android.graphics.drawable.Drawable
import android.util.Log
-import android.view.Display
+import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.ScrollCaptureResponse
import android.view.View
@@ -31,53 +30,53 @@
import android.view.WindowInsets
import android.window.OnBackInvokedCallback
import android.window.OnBackInvokedDispatcher
+import androidx.appcompat.content.res.AppCompatResources
import com.android.internal.logging.UiEventLogger
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.res.R
+import com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS
+import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
/**
* Legacy implementation of screenshot view methods. Just proxies the calls down into the original
* ScreenshotView.
*/
-class LegacyScreenshotViewProxy(context: Context) : ScreenshotViewProxy {
+class LegacyScreenshotViewProxy
+@AssistedInject
+constructor(
+ private val logger: UiEventLogger,
+ flags: FeatureFlags,
+ @Assisted private val context: Context,
+ @Assisted private val displayId: Int
+) : ScreenshotViewProxy {
override val view: ScreenshotView =
LayoutInflater.from(context).inflate(R.layout.screenshot, null) as ScreenshotView
override val screenshotPreview: View
-
- override var defaultDisplay: Int = Display.DEFAULT_DISPLAY
- set(value) {
- view.setDefaultDisplay(value)
- }
- override var defaultTimeoutMillis: Long = 6000
- set(value) {
- view.setDefaultTimeoutMillis(value)
- }
- override var onBackInvokedCallback: OnBackInvokedCallback = OnBackInvokedCallback {
- Log.wtf(TAG, "OnBackInvoked called before being set!")
- }
- override var onKeyListener: View.OnKeyListener? = null
- set(value) {
- view.setOnKeyListener(value)
- }
- override var flags: FeatureFlags? = null
- set(value) {
- view.setFlags(value)
- }
override var packageName: String = ""
set(value) {
+ field = value
view.setPackageName(value)
}
- override var logger: UiEventLogger? = null
- set(value) {
- view.setUiEventLogger(value)
- }
override var callbacks: ScreenshotView.ScreenshotViewCallback? = null
set(value) {
+ field = value
view.setCallbacks(value)
}
override var screenshot: ScreenshotData? = null
set(value) {
- view.setScreenshot(value)
+ field = value
+ value?.let {
+ val badgeBg =
+ AppCompatResources.getDrawable(context, R.drawable.overlay_badge_background)
+ val user = it.userHandle
+ if (badgeBg != null && user != null) {
+ view.badgeScreenshot(context.packageManager.getUserBadgedIcon(badgeBg, user))
+ }
+ view.setScreenshot(it)
+ }
}
override val isAttachedToWindow
@@ -88,10 +87,108 @@
get() = view.isPendingSharedTransition
init {
+ view.setUiEventLogger(logger)
+ view.setDefaultDisplay(displayId)
+ view.setFlags(flags)
+ addPredictiveBackListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
+ setOnKeyListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
+ if (LogConfig.DEBUG_WINDOW) {
+ Log.d(TAG, "adding OnComputeInternalInsetsListener")
+ }
+ view.viewTreeObserver.addOnComputeInternalInsetsListener(view)
+ screenshotPreview = view.screenshotPreview
+ }
+ override fun reset() = view.reset()
+ override fun updateInsets(insets: WindowInsets) = view.updateInsets(insets)
+ override fun updateOrientation(insets: WindowInsets) = view.updateOrientation(insets)
+
+ override fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator =
+ view.createScreenshotDropInAnimation(screenRect, showFlash)
+
+ override fun addQuickShareChip(quickShareAction: Notification.Action) =
+ view.addQuickShareChip(quickShareAction)
+
+ override fun setChipIntents(imageData: ScreenshotController.SavedImageData) =
+ view.setChipIntents(imageData)
+
+ override fun requestDismissal(event: ScreenshotEvent) {
+ if (DEBUG_DISMISS) {
+ Log.d(TAG, "screenshot dismissal requested")
+ }
+ // If we're already animating out, don't restart the animation
+ if (view.isDismissing) {
+ if (DEBUG_DISMISS) {
+ Log.v(TAG, "Already dismissing, ignoring duplicate command $event")
+ }
+ return
+ }
+ logger.log(event, 0, packageName)
+ view.animateDismissal()
+ }
+
+ override fun showScrollChip(packageName: String, onClick: Runnable) =
+ view.showScrollChip(packageName, onClick)
+
+ override fun hideScrollChip() = view.hideScrollChip()
+
+ override fun prepareScrollingTransition(
+ response: ScrollCaptureResponse,
+ screenBitmap: Bitmap,
+ newScreenshot: Bitmap,
+ screenshotTakenInPortrait: Boolean,
+ onTransitionPrepared: Runnable,
+ ) {
+ view.prepareScrollingTransition(
+ response,
+ screenBitmap,
+ newScreenshot,
+ screenshotTakenInPortrait
+ )
+ view.post { onTransitionPrepared.run() }
+ }
+
+ override fun startLongScreenshotTransition(
+ transitionDestination: Rect,
+ onTransitionEnd: Runnable,
+ longScreenshot: ScrollCaptureController.LongScreenshot
+ ) = view.startLongScreenshotTransition(transitionDestination, onTransitionEnd, longScreenshot)
+
+ override fun restoreNonScrollingUi() = view.restoreNonScrollingUi()
+
+ override fun stopInputListening() = view.stopInputListening()
+
+ override fun requestFocus() {
+ view.requestFocus()
+ }
+
+ override fun announceForAccessibility(string: String) = view.announceForAccessibility(string)
+
+ override fun prepareEntranceAnimation(runnable: Runnable) {
+ view.viewTreeObserver.addOnPreDrawListener(
+ object : ViewTreeObserver.OnPreDrawListener {
+ override fun onPreDraw(): Boolean {
+ if (LogConfig.DEBUG_WINDOW) {
+ Log.d(TAG, "onPreDraw: startAnimation")
+ }
+ view.viewTreeObserver.removeOnPreDrawListener(this)
+ runnable.run()
+ return true
+ }
+ }
+ )
+ }
+
+ private fun addPredictiveBackListener(onDismissRequested: (ScreenshotEvent) -> Unit) {
+ val onBackInvokedCallback = OnBackInvokedCallback {
+ if (LogConfig.DEBUG_INPUT) {
+ Log.d(TAG, "Predictive Back callback dispatched")
+ }
+ onDismissRequested.invoke(SCREENSHOT_DISMISSED_OTHER)
+ }
view.addOnAttachStateChangeListener(
object : View.OnAttachStateChangeListener {
- override fun onViewAttachedToWindow(view: View) {
+ override fun onViewAttachedToWindow(v: View) {
if (LogConfig.DEBUG_INPUT) {
Log.d(TAG, "Registering Predictive Back callback")
}
@@ -113,74 +210,27 @@
}
}
)
- if (LogConfig.DEBUG_WINDOW) {
- Log.d(TAG, "adding OnComputeInternalInsetsListener")
- }
- view.viewTreeObserver.addOnComputeInternalInsetsListener(view)
- screenshotPreview = view.screenshotPreview
}
-
- override fun reset() = view.reset()
- override fun updateInsets(insets: WindowInsets) = view.updateInsets(insets)
- override fun updateOrientation(insets: WindowInsets) = view.updateOrientation(insets)
-
- override fun badgeScreenshot(userBadgedIcon: Drawable) = view.badgeScreenshot(userBadgedIcon)
-
- override fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator =
- view.createScreenshotDropInAnimation(screenRect, showFlash)
-
- override fun addQuickShareChip(quickShareAction: Notification.Action) =
- view.addQuickShareChip(quickShareAction)
-
- override fun setChipIntents(imageData: ScreenshotController.SavedImageData) =
- view.setChipIntents(imageData)
-
- override fun animateDismissal() = view.animateDismissal()
-
- override fun showScrollChip(packageName: String, onClick: Runnable) =
- view.showScrollChip(packageName, onClick)
-
- override fun hideScrollChip() = view.hideScrollChip()
-
- override fun prepareScrollingTransition(
- response: ScrollCaptureResponse,
- screenBitmap: Bitmap,
- newScreenshot: Bitmap,
- screenshotTakenInPortrait: Boolean
- ) =
- view.prepareScrollingTransition(
- response,
- screenBitmap,
- newScreenshot,
- screenshotTakenInPortrait
+ private fun setOnKeyListener(onDismissRequested: (ScreenshotEvent) -> Unit) {
+ view.setOnKeyListener(
+ object : View.OnKeyListener {
+ override fun onKey(view: View, keyCode: Int, event: KeyEvent): Boolean {
+ if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
+ if (LogConfig.DEBUG_INPUT) {
+ Log.d(TAG, "onKeyEvent: $keyCode")
+ }
+ onDismissRequested.invoke(SCREENSHOT_DISMISSED_OTHER)
+ return true
+ }
+ return false
+ }
+ }
)
-
- override fun startLongScreenshotTransition(
- transitionDestination: Rect,
- onTransitionEnd: Runnable,
- longScreenshot: ScrollCaptureController.LongScreenshot
- ) = view.startLongScreenshotTransition(transitionDestination, onTransitionEnd, longScreenshot)
-
- override fun restoreNonScrollingUi() = view.restoreNonScrollingUi()
-
- override fun stopInputListening() = view.stopInputListening()
-
- override fun requestFocus() {
- view.requestFocus()
}
- override fun announceForAccessibility(string: String) = view.announceForAccessibility(string)
-
- override fun getViewTreeObserver(): ViewTreeObserver = view.viewTreeObserver
-
- override fun post(runnable: Runnable) {
- view.post(runnable)
- }
-
- class Factory : ScreenshotViewProxy.Factory {
- override fun getProxy(context: Context): ScreenshotViewProxy {
- return LegacyScreenshotViewProxy(context)
- }
+ @AssistedFactory
+ interface Factory : ScreenshotViewProxy.Factory {
+ override fun getProxy(context: Context, displayId: Int): LegacyScreenshotViewProxy
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 1ca9b98..198a29c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -21,7 +21,6 @@
import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
-import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT;
import static com.android.systemui.screenshot.LogConfig.DEBUG_UI;
import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW;
@@ -64,7 +63,6 @@
import android.view.Display;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
-import android.view.KeyEvent;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.ScrollCaptureResponse;
@@ -230,7 +228,7 @@
// From WizardManagerHelper.java
private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete";
- private static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000;
+ static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000;
private final WindowContext mContext;
private final FeatureFlags mFlags;
@@ -333,12 +331,7 @@
mScreenshotHandler = timeoutHandler;
mScreenshotHandler.setDefaultTimeoutMillis(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS);
- mScreenshotHandler.setOnTimeoutRunnable(() -> {
- if (DEBUG_UI) {
- Log.d(TAG, "Corner timeout hit");
- }
- dismissScreenshot(SCREENSHOT_INTERACTION_TIMEOUT);
- });
+
mDisplayId = displayId;
mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
@@ -351,7 +344,14 @@
mMessageContainerController = messageContainerController;
mAssistContentRequester = assistContentRequester;
- mViewProxy = viewProxyFactory.getProxy(mContext);
+ mViewProxy = viewProxyFactory.getProxy(mContext, mDisplayId);
+
+ mScreenshotHandler.setOnTimeoutRunnable(() -> {
+ if (DEBUG_UI) {
+ Log.d(TAG, "Corner timeout hit");
+ }
+ mViewProxy.requestDismissal(SCREENSHOT_INTERACTION_TIMEOUT);
+ });
mAccessibilityManager = AccessibilityManager.getInstance(mContext);
@@ -376,7 +376,7 @@
@Override
public void onReceive(Context context, Intent intent) {
if (ClipboardOverlayController.COPY_OVERLAY_ACTION.equals(intent.getAction())) {
- dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
+ mViewProxy.requestDismissal(SCREENSHOT_DISMISSED_OTHER);
}
}
};
@@ -460,7 +460,7 @@
attachWindow();
- boolean showFlash = true;
+ boolean showFlash;
if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) {
if (screenshot.getScreenBounds() != null
&& aspectRatiosMatch(screenshot.getBitmap(), screenshot.getInsets(),
@@ -472,15 +472,14 @@
screenshot.setScreenBounds(new Rect(0, 0, screenshot.getBitmap().getWidth(),
screenshot.getBitmap().getHeight()));
}
+ } else {
+ showFlash = true;
}
- prepareAnimation(screenshot.getScreenBounds(), showFlash, () -> {
- mMessageContainerController.onScreenshotTaken(screenshot);
- });
+ mViewProxy.prepareEntranceAnimation(
+ () -> startAnimation(screenshot.getScreenBounds(), showFlash,
+ () -> mMessageContainerController.onScreenshotTaken(screenshot)));
- mViewProxy.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
- mContext.getDrawable(R.drawable.overlay_badge_background),
- screenshot.getUserHandle()));
mViewProxy.setScreenshot(screenshot);
// ignore system bar insets for the purpose of window layout
@@ -525,22 +524,11 @@
}
/**
- * Clears current screenshot
+ * Requests the view to dismiss the current screenshot (may be ignored, if screenshot is already
+ * being dismissed)
*/
- void dismissScreenshot(ScreenshotEvent event) {
- if (DEBUG_DISMISS) {
- Log.d(TAG, "dismissScreenshot");
- }
- // If we're already animating out, don't restart the animation
- if (mViewProxy.isDismissing()) {
- if (DEBUG_DISMISS) {
- Log.v(TAG, "Already dismissing, ignoring duplicate command");
- }
- return;
- }
- mUiEventLogger.log(event, 0, mPackageName);
- mScreenshotHandler.cancelTimeout();
- mViewProxy.animateDismissal();
+ void requestDismissal(ScreenshotEvent event) {
+ mViewProxy.requestDismissal(event);
}
boolean isPendingSharedTransition() {
@@ -572,10 +560,6 @@
mScreenshotSoundController.releaseScreenshotSoundAsync();
}
- private void respondToKeyDismissal() {
- dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
- }
-
/**
* Update resources on configuration change. Reinflate for theme/color changes.
*/
@@ -585,13 +569,6 @@
}
mMessageContainerController.setView(mViewProxy.getView());
- mViewProxy.setLogger(mUiEventLogger);
- mViewProxy.setOnBackInvokedCallback(() -> {
- if (DEBUG_INPUT) {
- Log.d(TAG, "Predictive Back callback dispatched");
- }
- respondToKeyDismissal();
- });
mViewProxy.setCallbacks(new ScreenshotView.ScreenshotViewCallback() {
@Override
public void onUserInteraction() {
@@ -618,20 +595,6 @@
setWindowFocusable(false);
}
});
- mViewProxy.setFlags(mFlags);
- mViewProxy.setDefaultDisplay(mDisplayId);
- mViewProxy.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis());
-
- mViewProxy.setOnKeyListener((v, keyCode, event) -> {
- if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
- if (DEBUG_INPUT) {
- Log.d(TAG, "onKeyEvent: " + keyCode);
- }
- respondToKeyDismissal();
- return true;
- }
- return false;
- });
if (DEBUG_WINDOW) {
Log.d(TAG, "setContentView: " + mViewProxy.getView());
@@ -639,22 +602,6 @@
setContentView(mViewProxy.getView());
}
- private void prepareAnimation(Rect screenRect, boolean showFlash,
- Runnable onAnimationComplete) {
- mViewProxy.getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- if (DEBUG_WINDOW) {
- Log.d(TAG, "onPreDraw: startAnimation");
- }
- mViewProxy.getViewTreeObserver().removeOnPreDrawListener(this);
- startAnimation(screenRect, showFlash, onAnimationComplete);
- return true;
- }
- });
- }
-
private void enqueueScrollCaptureRequest(UserHandle owner) {
// Wait until this window is attached to request because it is
// the reference used to locate the target window (below).
@@ -739,10 +686,14 @@
Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplayId,
new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));
- mViewProxy.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
- mScreenshotTakenInPortrait);
- // delay starting scroll capture to make sure the scrim is up before the app moves
- mViewProxy.post(() -> runBatchScrollCapture(response, owner));
+ if (newScreenshot != null) {
+ // delay starting scroll capture to make sure scrim is up before the app moves
+ mViewProxy.prepareScrollingTransition(
+ response, mScreenBitmap, newScreenshot, mScreenshotTakenInPortrait,
+ () -> runBatchScrollCapture(response, owner));
+ } else {
+ Log.wtf(TAG, "failed to capture current screenshot for scroll transition");
+ }
});
} catch (InterruptedException | ExecutionException e) {
Log.e(TAG, "requestScrollCapture failed", e);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 8a8766d..1c5a8a1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -26,6 +26,7 @@
import static com.android.systemui.screenshot.LogConfig.DEBUG_UI;
import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW;
import static com.android.systemui.screenshot.LogConfig.logTag;
+import static com.android.systemui.screenshot.ScreenshotController.SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS;
import static java.util.Objects.requireNonNull;
@@ -33,6 +34,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.BroadcastOptions;
import android.app.Notification;
@@ -168,7 +170,6 @@
private ScreenshotData mScreenshotData;
private final InteractionJankMonitor mInteractionJankMonitor;
- private long mDefaultTimeoutOfTimeoutHandler;
private FeatureFlags mFlags;
private final Bundle mInteractiveBroadcastOption;
@@ -244,10 +245,6 @@
return InteractionJankMonitor.getInstance();
}
- void setDefaultTimeoutMillis(long timeout) {
- mDefaultTimeoutOfTimeoutHandler = timeout;
- }
-
public void hideScrollChip() {
mScrollChip.setVisibility(View.GONE);
}
@@ -755,7 +752,7 @@
InteractionJankMonitor.Configuration.Builder.withView(
CUJ_TAKE_SCREENSHOT, mScreenshotStatic)
.setTag("Actions")
- .setTimeout(mDefaultTimeoutOfTimeoutHandler);
+ .setTimeout(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS);
mInteractionJankMonitor.begin(builder);
}
});
@@ -781,7 +778,7 @@
return animator;
}
- void badgeScreenshot(Drawable badge) {
+ void badgeScreenshot(@Nullable Drawable badge) {
mScreenshotBadge.setImageDrawable(badge);
mScreenshotBadge.setVisibility(badge != null ? View.VISIBLE : View.GONE);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
index 381404a..182b889 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
@@ -21,29 +21,17 @@
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Rect
-import android.graphics.drawable.Drawable
import android.view.ScrollCaptureResponse
import android.view.View
-import android.view.View.OnKeyListener
import android.view.ViewGroup
-import android.view.ViewTreeObserver
import android.view.WindowInsets
-import android.window.OnBackInvokedCallback
-import com.android.internal.logging.UiEventLogger
-import com.android.systemui.flags.FeatureFlags
/** Abstraction of the surface between ScreenshotController and ScreenshotView */
interface ScreenshotViewProxy {
val view: ViewGroup
val screenshotPreview: View
- var defaultDisplay: Int
- var defaultTimeoutMillis: Long
- var onBackInvokedCallback: OnBackInvokedCallback
- var onKeyListener: OnKeyListener?
- var flags: FeatureFlags?
var packageName: String
- var logger: UiEventLogger?
var callbacks: ScreenshotView.ScreenshotViewCallback?
var screenshot: ScreenshotData?
@@ -54,11 +42,10 @@
fun reset()
fun updateInsets(insets: WindowInsets)
fun updateOrientation(insets: WindowInsets)
- fun badgeScreenshot(userBadgedIcon: Drawable)
fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator
fun addQuickShareChip(quickShareAction: Notification.Action)
fun setChipIntents(imageData: ScreenshotController.SavedImageData)
- fun animateDismissal()
+ fun requestDismissal(event: ScreenshotEvent)
fun showScrollChip(packageName: String, onClick: Runnable)
fun hideScrollChip()
@@ -66,7 +53,8 @@
response: ScrollCaptureResponse,
screenBitmap: Bitmap,
newScreenshot: Bitmap,
- screenshotTakenInPortrait: Boolean
+ screenshotTakenInPortrait: Boolean,
+ onTransitionPrepared: Runnable,
)
fun startLongScreenshotTransition(
transitionDestination: Rect,
@@ -78,10 +66,9 @@
fun stopInputListening()
fun requestFocus()
fun announceForAccessibility(string: String)
- fun getViewTreeObserver(): ViewTreeObserver
- fun post(runnable: Runnable)
+ fun prepareEntranceAnimation(runnable: Runnable)
interface Factory {
- fun getProxy(context: Context): ScreenshotViewProxy
+ fun getProxy(context: Context, displayId: Int): ScreenshotViewProxy
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index e464fd0..bc33755 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -136,7 +136,7 @@
fun onCloseSystemDialogsReceived() {
screenshotControllers.forEach { (_, screenshotController) ->
if (!screenshotController.isPendingSharedTransition) {
- screenshotController.dismissScreenshot(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER)
+ screenshotController.requestDismissal(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 0991c9a..9cf347b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -53,9 +53,9 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.util.ScreenshotRequest;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.res.R;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -89,7 +89,7 @@
// TODO(b/295143676): move receiver inside executor when the flag is enabled.
mTakeScreenshotExecutor.get().onCloseSystemDialogsReceived();
} else if (!mScreenshot.isPendingSharedTransition()) {
- mScreenshot.dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
+ mScreenshot.requestDismissal(SCREENSHOT_DISMISSED_OTHER);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java b/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java
index 71c2cb4..5df6c45 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java
@@ -40,7 +40,7 @@
private final Context mContext;
private Runnable mOnTimeout;
- private int mDefaultTimeout = DEFAULT_TIMEOUT_MILLIS;
+ int mDefaultTimeout = DEFAULT_TIMEOUT_MILLIS;
@Inject
public TimeoutHandler(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index a00c81d..cdb9abb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -86,7 +86,8 @@
ScreenshotSoundControllerImpl screenshotSoundProviderImpl);
@Provides
- static ScreenshotViewProxy.Factory providesScreenshotViewProxyFactory() {
- return new LegacyScreenshotViewProxy.Factory();
+ static ScreenshotViewProxy.Factory providesScreenshotViewProxyFactory(
+ LegacyScreenshotViewProxy.Factory legacyScreenshotViewProxyFactory) {
+ return legacyScreenshotViewProxyFactory;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index e92630f..92d6ec97 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -63,7 +63,7 @@
public class BrightnessController implements ToggleSlider.Listener, MirroredBrightnessController {
private static final String TAG = "CentralSurfaces.BrightnessController";
- private static final int SLIDER_ANIMATION_DURATION = 1000;
+ private static final int SLIDER_ANIMATION_DURATION = 3000;
private static final int MSG_UPDATE_SLIDER = 1;
private static final int MSG_ATTACH_LISTENER = 2;
@@ -96,6 +96,7 @@
};
private volatile boolean mAutomatic; // Brightness adjusted automatically using ambient light.
+ private boolean mTrackingTouch = false; // Brightness adjusted via touch events.
private volatile boolean mIsVrModeEnabled;
private boolean mListening;
private boolean mExternalChange;
@@ -330,6 +331,7 @@
@Override
public void onChanged(boolean tracking, int value, boolean stopTracking) {
+ mTrackingTouch = tracking;
if (mExternalChange) return;
if (mSliderAnimator != null) {
@@ -396,6 +398,12 @@
}
}
+ private boolean triggeredByBrightnessKey() {
+ // When the brightness mode is manual and the user isn't changing the brightness via the
+ // brightness slider, assume changes are coming from a brightness key.
+ return !mAutomatic && !mTrackingTouch;
+ }
+
private void updateSlider(float brightnessValue, boolean inVrMode) {
final float min = mBrightnessMin;
final float max = mBrightnessMax;
@@ -417,12 +425,13 @@
}
private void animateSliderTo(int target) {
- if (!mControlValueInitialized || !mControl.isVisible()) {
+ if (!mControlValueInitialized || !mControl.isVisible() || triggeredByBrightnessKey()) {
// Don't animate the first value since its default state isn't meaningful to users.
// We also don't want to animate slider if it's not visible - especially important when
// two sliders are active at the same time in split shade (one in QS and one in QQS),
// as this negatively affects transition between them and they share mirror slider -
- // animating it from two different sources causes janky motion
+ // animating it from two different sources causes janky motion.
+ // Don't animate if the value is changed via the brightness keys of a keyboard.
mControl.setValue(target);
mControlValueInitialized = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index 861a2ed..539b0c2 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -19,6 +19,7 @@
import static com.android.systemui.Flags.hapticBrightnessSlider;
import android.content.Context;
+import android.content.Intent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -33,6 +34,7 @@
import com.android.systemui.classifier.Classifier;
import com.android.systemui.haptics.slider.HapticSliderViewBinder;
import com.android.systemui.haptics.slider.SeekableSliderHapticPlugin;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.VibratorHelper;
@@ -62,6 +64,7 @@
private final UiEventLogger mUiEventLogger;
private final SeekableSliderHapticPlugin mBrightnessSliderHapticPlugin;
+ private final ActivityStarter mActivityStarter;
private final Gefingerpoken mOnInterceptListener = new Gefingerpoken() {
@Override
@@ -84,11 +87,13 @@
BrightnessSliderView brightnessSliderView,
FalsingManager falsingManager,
UiEventLogger uiEventLogger,
- SeekableSliderHapticPlugin brightnessSliderHapticPlugin) {
+ SeekableSliderHapticPlugin brightnessSliderHapticPlugin,
+ ActivityStarter activityStarter) {
super(brightnessSliderView);
mFalsingManager = falsingManager;
mUiEventLogger = uiEventLogger;
mBrightnessSliderHapticPlugin = brightnessSliderHapticPlugin;
+ mActivityStarter = activityStarter;
}
/**
@@ -131,7 +136,15 @@
@Override
public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
- mView.setEnforcedAdmin(admin);
+ if (admin == null) {
+ mView.setAdminBlocker(null);
+ } else {
+ mView.setAdminBlocker(() -> {
+ Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(admin);
+ mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
+ return true;
+ });
+ }
}
private void setMirror(ToggleSlider toggleSlider) {
@@ -259,18 +272,21 @@
private final UiEventLogger mUiEventLogger;
private final VibratorHelper mVibratorHelper;
private final SystemClock mSystemClock;
+ private final ActivityStarter mActivityStarter;
@Inject
public Factory(
FalsingManager falsingManager,
UiEventLogger uiEventLogger,
VibratorHelper vibratorHelper,
- SystemClock clock
+ SystemClock clock,
+ ActivityStarter activityStarter
) {
mFalsingManager = falsingManager;
mUiEventLogger = uiEventLogger;
mVibratorHelper = vibratorHelper;
mSystemClock = clock;
+ mActivityStarter = activityStarter;
}
/**
@@ -292,7 +308,8 @@
if (hapticBrightnessSlider()) {
HapticSliderViewBinder.bind(viewRoot, plugin);
}
- return new BrightnessSliderController(root, mFalsingManager, mUiEventLogger, plugin);
+ return new BrightnessSliderController(
+ root, mFalsingManager, mUiEventLogger, plugin, mActivityStarter);
}
/** Get the layout to inflate based on what slider to use */
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
index c43d20c..92006a4 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
@@ -31,7 +31,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.settingslib.RestrictedLockUtils;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.res.R;
@@ -120,9 +119,8 @@
* @param admin
* @see ToggleSeekBar#setEnforcedAdmin
*/
- public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
- mSlider.setEnabled(admin == null);
- mSlider.setEnforcedAdmin(admin);
+ void setAdminBlocker(ToggleSeekBar.AdminBlocker blocker) {
+ mSlider.setAdminBlocker(blocker);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
index a5a0ae7..288ff09 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
@@ -17,20 +17,15 @@
package com.android.systemui.settings.brightness;
import android.content.Context;
-import android.content.Intent;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.SeekBar;
-import com.android.settingslib.RestrictedLockUtils;
-import com.android.systemui.Dependency;
-import com.android.systemui.plugins.ActivityStarter;
-
public class ToggleSeekBar extends SeekBar {
private String mAccessibilityLabel;
- private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin = null;
+ private AdminBlocker mAdminBlocker;
public ToggleSeekBar(Context context) {
super(context);
@@ -46,10 +41,7 @@
@Override
public boolean onTouchEvent(MotionEvent event) {
- if (mEnforcedAdmin != null) {
- Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
- mContext, mEnforcedAdmin);
- Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(intent, 0);
+ if (mAdminBlocker != null && mAdminBlocker.block()) {
return true;
}
if (!isEnabled()) {
@@ -71,7 +63,12 @@
}
}
- public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
- mEnforcedAdmin = admin;
+ void setAdminBlocker(AdminBlocker blocker) {
+ mAdminBlocker = blocker;
+ setEnabled(blocker == null);
+ }
+
+ interface AdminBlocker {
+ boolean block();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index d3869ba..3169e9c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -33,6 +33,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import com.android.systemui.util.kotlin.collectFlow
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -47,6 +48,7 @@
constructor(
private val communalInteractor: CommunalInteractor,
private val communalViewModel: CommunalViewModel,
+ private val dialogFactory: SystemUIDialogFactory,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val shadeInteractor: ShadeInteractor,
private val powerManager: PowerManager,
@@ -119,7 +121,14 @@
): View {
return initView(
ComposeView(context).apply {
- setContent { PlatformTheme { CommunalContainer(viewModel = communalViewModel) } }
+ setContent {
+ PlatformTheme {
+ CommunalContainer(
+ viewModel = communalViewModel,
+ dialogFactory = dialogFactory,
+ )
+ }
+ }
}
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index cdb520f..8b791de 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1540,13 +1540,13 @@
mKeyguardBottomArea = keyguardBottomArea;
}
- @Override
+ /** Sets a listener to be notified when the shade starts opening or finishes closing. */
public void setOpenCloseListener(OpenCloseListener openCloseListener) {
SceneContainerFlag.assertInLegacyMode();
mOpenCloseListener = openCloseListener;
}
- @Override
+ /** Sets a listener to be notified when touch tracking begins. */
public void setTrackingStartedListener(TrackingStartedListener trackingStartedListener) {
mTrackingStartedListener = trackingStartedListener;
}
@@ -1753,10 +1753,11 @@
}
private void updateKeyguardStatusViewAlignment(boolean animate) {
+ boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered();
if (migrateClocksToBlueprint()) {
+ mKeyguardInteractor.setClockShouldBeCentered(shouldBeCentered);
return;
}
- boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered();
ConstraintLayout layout = mNotificationContainerParent;
mKeyguardStatusViewController.updateAlignment(
layout, mSplitShadeEnabled, shouldBeCentered, animate);
@@ -2603,7 +2604,6 @@
return maxHeight;
}
- @Override
public boolean isExpandingOrCollapsing() {
float lockscreenExpansionProgress = mQsController.getLockscreenShadeDragProgress();
return mIsExpandingOrCollapsing
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt b/packages/SystemUI/src/com/android/systemui/shade/OpenCloseListener.kt
similarity index 66%
rename from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt
rename to packages/SystemUI/src/com/android/systemui/shade/OpenCloseListener.kt
index 98a9e93..108dd478 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/OpenCloseListener.kt
@@ -14,16 +14,13 @@
* limitations under the License.
*/
-package com.android.credentialmanager.ui.screens
+package com.android.systemui.shade
-import androidx.activity.result.IntentSenderRequest
+/** Listens for when shade begins opening or finishes closing. */
+interface OpenCloseListener {
+ /** Called when the shade finishes closing. */
+ fun onClosingFinished()
-sealed class UiState {
- data object CredentialScreen : UiState()
-
- data class CredentialSelected(
- val intentSenderRequest: IntentSenderRequest?
- ) : UiState()
-
- data object Cancel : UiState()
+ /** Called when the shade starts opening. */
+ fun onOpenStarted()
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
index 27168a7..177c3db 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -89,8 +89,7 @@
override fun isShadeFullyOpen(): Boolean = shadeInteractor.isAnyFullyExpanded.value
- override fun isExpandingOrCollapsing(): Boolean =
- shadeInteractor.anyExpansion.value > 0f && shadeInteractor.anyExpansion.value < 1f
+ override fun isExpandingOrCollapsing(): Boolean = shadeInteractor.isUserInteracting.value
override fun instantExpandShade() {
// Do nothing
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLockscreenInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLockscreenInteractor.kt
index a9ba6f9..859fce5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLockscreenInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLockscreenInteractor.kt
@@ -27,9 +27,6 @@
*/
@Deprecated("Use ShadeInteractor instead") fun expandToNotifications()
- /** Returns whether the shade is expanding or collapsing itself or quick settings. */
- val isExpandingOrCollapsing: Boolean
-
/**
* Returns whether the shade height is greater than zero (i.e. partially or fully expanded),
* there is a HUN, the shade is animating, or the shade is instantly expanding.
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index 07236d1..a343ded 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -31,18 +31,12 @@
* @see NotificationPanelViewController
*/
interface ShadeViewController {
- /** Returns whether the shade is expanding or collapsing itself or quick settings. */
- val isExpandingOrCollapsing: Boolean
-
/**
* Returns whether the shade height is greater than zero or the shade is expecting a synthesized
* down event.
*/
val isPanelExpanded: Boolean
- /** Returns whether the shade is in the process of collapsing. */
- val isCollapsing: Boolean
-
/** Returns whether shade's height is zero. */
val isFullyCollapsed: Boolean
@@ -52,15 +46,9 @@
/** Returns whether the shade's top level view is enabled. */
val isViewEnabled: Boolean
- /** Sets a listener to be notified when the shade starts opening or finishes closing. */
- fun setOpenCloseListener(openCloseListener: OpenCloseListener)
-
/** Returns whether status bar icons should be hidden when the shade is expanded. */
fun shouldHideStatusBarIconsWhenExpanded(): Boolean
- /** Sets a listener to be notified when touch tracking begins. */
- fun setTrackingStartedListener(trackingStartedListener: TrackingStartedListener)
-
/**
* Disables the shade header.
*
@@ -111,19 +99,6 @@
fun showAodUi()
/**
- * This method should not be used anymore, you should probably use [.isShadeFullyOpen] instead.
- * It was overused as indicating if shade is open or we're on keyguard/AOD. Moving forward we
- * should be explicit about the what state we're checking.
- *
- * @return if panel is covering the screen, which means we're in expanded shade or keyguard/AOD
- */
- @Deprecated(
- "depends on the state you check, use {@link #isShadeFullyExpanded()},\n" +
- "{@link #isOnAod()}, {@link #isOnKeyguard()} instead."
- )
- fun isFullyExpanded(): Boolean
-
- /**
* Sends an external (e.g. Status Bar) touch event to the Shade touch handler.
*
* This is different from [startInputFocusTransfer] as it doesn't rely on setting the launcher
@@ -250,17 +225,3 @@
/** Return the fraction of the shade that's expanded, when in lockscreen. */
val lockscreenShadeDragProgress: Float
}
-
-/** Listens for when touch tracking begins. */
-interface TrackingStartedListener {
- fun onTrackingStarted()
-}
-
-/** Listens for when shade begins opening or finishes closing. */
-interface OpenCloseListener {
- /** Called when the shade finishes closing. */
- fun onClosingFinished()
-
- /** Called when the shade starts opening. */
- fun onOpenStarted()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index 6e03686..c9140b5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -33,34 +33,33 @@
ShadeBackActionInteractor,
ShadeLockscreenInteractor,
PanelExpansionInteractor {
- override fun expandToNotifications() {}
- override val isExpandingOrCollapsing: Boolean = false
- override val isExpanded: Boolean = false
+ @Deprecated("Use ShadeInteractor instead") override fun expandToNotifications() {}
+ @Deprecated("Use ShadeInteractor instead") override val isExpanded: Boolean = false
override val isPanelExpanded: Boolean = false
override fun animateCollapseQs(fullyCollapse: Boolean) {}
override fun canBeCollapsed(): Boolean = false
- override val isCollapsing: Boolean = false
+ @Deprecated("Use ShadeAnimationInteractor instead") override val isCollapsing: Boolean = false
override val isFullyCollapsed: Boolean = false
override val isTracking: Boolean = false
override val isViewEnabled: Boolean = false
- override fun setOpenCloseListener(openCloseListener: OpenCloseListener) {}
override fun shouldHideStatusBarIconsWhenExpanded() = false
- override fun blockExpansionForCurrentTouch() {}
- override fun setTrackingStartedListener(trackingStartedListener: TrackingStartedListener) {}
+ @Deprecated("Not supported by scenes") override fun blockExpansionForCurrentTouch() {}
override fun disableHeader(state1: Int, state2: Int, animated: Boolean) {}
override fun startExpandLatencyTracking() {}
override fun startBouncerPreHideAnimation() {}
override fun dozeTimeTick() {}
override fun resetViews(animate: Boolean) {}
override val barState: Int = 0
+ @Deprecated("Only supported by very old devices that will not adopt scenes.")
override fun closeUserSwitcherIfOpen(): Boolean {
return false
}
override fun onBackPressed() {}
+ @Deprecated("According to b/318376223, shade predictive back is not be supported.")
override fun onBackProgressed(progressFraction: Float) {}
override fun setAlpha(alpha: Int, animate: Boolean) {}
override fun setAlphaChangeAnimationEndAction(r: Runnable) {}
- override fun setPulsing(pulsing: Boolean) {}
+ @Deprecated("Not supported by scenes") override fun setPulsing(pulsing: Boolean) {}
override fun setQsScrimEnabled(qsScrimEnabled: Boolean) {}
override fun setAmbientIndicationTop(ambientIndicationTop: Int, ambientTextVisible: Boolean) {}
override fun updateSystemUiStateFlags() {}
@@ -69,14 +68,18 @@
override fun removeOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener) {}
override fun transitionToExpandedShade(delay: Long) {}
- override fun resetViewGroupFade() {}
+ @Deprecated("Not supported by scenes") override fun resetViewGroupFade() {}
+ @Deprecated("Not supported by scenes")
override fun setKeyguardTransitionProgress(keyguardAlpha: Float, keyguardTranslationY: Int) {}
- override fun setOverStretchAmount(amount: Float) {}
+ @Deprecated("Not supported by scenes") override fun setOverStretchAmount(amount: Float) {}
+ @Deprecated("TODO(b/325072511) delete this")
override fun setKeyguardStatusBarAlpha(alpha: Float) {}
override fun showAodUi() {}
- override fun isFullyExpanded(): Boolean {
- return false
- }
+ @Deprecated(
+ "depends on the state you check, use {@link #isShadeFullyExpanded()},\n" +
+ "{@link #isOnAod()}, {@link #isOnKeyguard()} instead."
+ )
+ override val isFullyExpanded = false
override fun handleExternalTouch(event: MotionEvent): Boolean {
return false
}
@@ -87,6 +90,7 @@
override val shadeHeadsUpTracker = ShadeHeadsUpTrackerEmptyImpl()
override val shadeFoldAnimator = ShadeFoldAnimatorEmptyImpl()
+ @Deprecated("Use SceneInteractor.currentScene instead.")
override val legacyPanelExpansion = flowOf(0f)
}
diff --git a/core/java/android/app/ondeviceintelligence/Content.aidl b/packages/SystemUI/src/com/android/systemui/shade/TrackingStartedListener.kt
similarity index 66%
copy from core/java/android/app/ondeviceintelligence/Content.aidl
copy to packages/SystemUI/src/com/android/systemui/shade/TrackingStartedListener.kt
index 40f0ef9..3803c27 100644
--- a/core/java/android/app/ondeviceintelligence/Content.aidl
+++ b/packages/SystemUI/src/com/android/systemui/shade/TrackingStartedListener.kt
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2024, The Android Open Source Project
+/*
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package android.app.ondeviceintelligence;
+package com.android.systemui.shade
-/**
- * @hide
- */
-parcelable Content;
+/** Listens for when touch tracking begins. */
+interface TrackingStartedListener {
+ fun onTrackingStarted()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index e050c0b..5c79e1e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shade.data.repository
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.shared.model.ShadeMode
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -100,8 +101,7 @@
@Deprecated("Use ShadeInteractor.isQsBypassingShade instead")
val legacyExpandImmediate: StateFlow<Boolean>
- /** Whether the current configuration requires the split shade to be shown. */
- val isSplitShade: StateFlow<Boolean>
+ val shadeMode: StateFlow<ShadeMode>
/** True when QS is taking up the entire screen, i.e. fully expanded on a non-unfolded phone. */
@Deprecated("Use ShadeInteractor instead") val legacyQsFullscreen: StateFlow<Boolean>
@@ -109,7 +109,7 @@
/** NPVC.mClosing as a flow. */
@Deprecated("Use ShadeAnimationInteractor instead") val legacyIsClosing: StateFlow<Boolean>
- fun setSplitShade(isSplitShade: Boolean)
+ fun setShadeMode(mode: ShadeMode)
/** Sets whether a closing animation is happening. */
@Deprecated("Use ShadeAnimationInteractor instead") fun setLegacyIsClosing(isClosing: Boolean)
@@ -219,11 +219,11 @@
@Deprecated("Use ShadeInteractor instead")
override val legacyQsFullscreen: StateFlow<Boolean> = _legacyQsFullscreen.asStateFlow()
- val _isSplitShade = MutableStateFlow(false)
- override val isSplitShade: StateFlow<Boolean> = _isSplitShade.asStateFlow()
+ val _shadeMode = MutableStateFlow<ShadeMode>(ShadeMode.Single)
+ override val shadeMode: StateFlow<ShadeMode> = _shadeMode.asStateFlow()
- override fun setSplitShade(isSplitShade: Boolean) {
- _isSplitShade.value = isSplitShade
+ override fun setShadeMode(shadeMode: ShadeMode) {
+ _shadeMode.value = shadeMode
}
override fun setLegacyQsFullscreen(legacyQsFullscreen: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt
index 01118bd..bd96a33 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt
@@ -41,4 +41,20 @@
* backwards-compatibility and should not be consumed by newer code.
*/
@Deprecated("Use SceneInteractor.currentScene instead.") val legacyPanelExpansion: Flow<Float>
+
+ /**
+ * This method should not be used anymore, you should probably use [.isShadeFullyOpen] instead.
+ * It was overused as indicating if shade is open or we're on keyguard/AOD. Moving forward we
+ * should be explicit about the what state we're checking.
+ *
+ * @return if panel is covering the screen, which means we're in expanded shade or keyguard/AOD
+ */
+ @Deprecated(
+ "depends on the state you check, use {@link #isShadeFullyExpanded()},\n" +
+ "{@link #isOnAod()}, {@link #isOnKeyguard()} instead."
+ )
+ val isFullyExpanded: Boolean
+
+ /** Returns whether the shade is in the process of collapsing. */
+ @Deprecated("Use ShadeAnimationInteractor instead") val isCollapsing: Boolean
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
index 20f73b0..c5a6968 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
@@ -35,6 +35,8 @@
@Inject
constructor(
sceneInteractor: SceneInteractor,
+ shadeInteractor: ShadeInteractor,
+ shadeAnimationInteractor: ShadeAnimationInteractor,
) : PanelExpansionInteractor {
/**
@@ -93,6 +95,16 @@
}
}
+ @Deprecated(
+ "depends on the state you check, use {@link #isShadeFullyExpanded()},\n" +
+ "{@link #isOnAod()}, {@link #isOnKeyguard()} instead."
+ )
+ override val isFullyExpanded = shadeInteractor.isAnyFullyExpanded.value
+
+ @Deprecated("Use ShadeAnimationInteractor instead")
+ override val isCollapsing =
+ shadeAnimationInteractor.isAnyCloseAnimationRunning.value ||
+ shadeAnimationInteractor.isLaunchingActivity.value
private fun SceneKey.isExpandable(): Boolean {
return this == Scenes.Shade || this == Scenes.QuickSettings
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt
index 5a777e8..134c983 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt
@@ -17,7 +17,6 @@
package com.android.systemui.shade.domain.interactor
import com.android.systemui.shade.data.repository.ShadeAnimationRepository
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -38,5 +37,5 @@
* completes the close. Important: if QS is collapsing back to shade, this will be false because
* that is not considered "closing".
*/
- abstract val isAnyCloseAnimationRunning: Flow<Boolean>
+ abstract val isAnyCloseAnimationRunning: StateFlow<Boolean>
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt
index 2a7658a..f364d6d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt
@@ -19,7 +19,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.shade.data.repository.ShadeAnimationRepository
import javax.inject.Inject
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.MutableStateFlow
/** Implementation of ShadeAnimationInteractor for shadeless SysUI variants. */
@SysUISingleton
@@ -28,5 +28,5 @@
constructor(
shadeAnimationRepository: ShadeAnimationRepository,
) : ShadeAnimationInteractor(shadeAnimationRepository) {
- override val isAnyCloseAnimationRunning = flowOf(false)
+ override val isAnyCloseAnimationRunning = MutableStateFlow(false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt
index eaac8ae..d9982e3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt
@@ -18,21 +18,26 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.ShadeAnimationRepository
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
/** Implementation of ShadeAnimationInteractor compatible with the scene container framework. */
@SysUISingleton
class ShadeAnimationInteractorSceneContainerImpl
@Inject
constructor(
+ @Background scope: CoroutineScope,
shadeAnimationRepository: ShadeAnimationRepository,
sceneInteractor: SceneInteractor,
) : ShadeAnimationInteractor(shadeAnimationRepository) {
@@ -56,4 +61,5 @@
}
}
.distinctUntilChanged()
+ .stateIn(scope, SharingStarted.Eagerly, false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index ad3fbe5..bc60c83 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shade.domain.interactor
+import com.android.systemui.shade.shared.model.ShadeMode
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
@@ -103,8 +104,7 @@
*/
val isUserInteractingWithQs: Flow<Boolean>
- /** Whether the current configuration requires the split shade to be shown. */
- val isSplitShade: StateFlow<Boolean>
+ val shadeMode: StateFlow<ShadeMode>
}
fun createAnyExpansionFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
index 57a36b5..e9bb4c6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shade.domain.interactor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.shared.model.ShadeMode
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -42,5 +43,5 @@
override val isUserInteracting: StateFlow<Boolean> = inactiveFlowBoolean
override val isShadeTouchable: Flow<Boolean> = inactiveFlowBoolean
override val isExpandToQsEnabled: Flow<Boolean> = inactiveFlowBoolean
- override val isSplitShade: StateFlow<Boolean> = inactiveFlowBoolean
+ override val shadeMode: StateFlow<ShadeMode> = MutableStateFlow(ShadeMode.Single)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
index 3bccd2b..6414af3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
@@ -21,6 +21,7 @@
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -99,7 +100,7 @@
override val isUserInteractingWithQs: Flow<Boolean> =
userInteractingFlow(repository.legacyQsTracking, repository.qsExpansion)
- override val isSplitShade: StateFlow<Boolean> = repository.isSplitShade
+ override val shadeMode: StateFlow<ShadeMode> = repository.shadeMode
/**
* Return a flow for whether a user is interacting with an expandable shade component using
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
index ad8c029..7785eda 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
@@ -23,6 +23,7 @@
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -108,7 +109,7 @@
override val isUserInteractingWithQs: Flow<Boolean> =
sceneBasedInteracting(sceneInteractor, Scenes.QuickSettings)
- override val isSplitShade: StateFlow<Boolean> = shadeRepository.isSplitShade
+ override val shadeMode: StateFlow<ShadeMode> = shadeRepository.shadeMode
/**
* Returns a flow that uses scene transition progress to and from a scene that is pulled down
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
index 1f78ae8..3d9337e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
@@ -38,8 +38,6 @@
changeToShadeScene()
}
- override val isExpandingOrCollapsing = shadeInteractor.isUserInteracting.value
-
override val isExpanded = shadeInteractor.isAnyExpanded.value
override fun startBouncerPreHideAnimation() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
index 334908e..11ce818 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
@@ -22,6 +22,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.policy.SplitShadeStateController
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -41,17 +42,25 @@
) : CoreStartable {
override fun start() {
- hydrateSplitShade()
+ hydrateShadeMode()
}
- private fun hydrateSplitShade() {
+ private fun hydrateShadeMode() {
applicationScope.launch {
configurationRepository.onAnyConfigurationChange
// Force initial collection.
.onStart { emit(Unit) }
.map { applicationContext.resources }
.map { resources -> controller.shouldUseSplitNotificationShade(resources) }
- .collect { isSplitShade -> shadeRepository.setSplitShade(isSplitShade) }
+ .collect { isSplitShade ->
+ shadeRepository.setShadeMode(
+ if (isSplitShade) {
+ ShadeMode.Split
+ } else {
+ ShadeMode.Single
+ }
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt
new file mode 100644
index 0000000..3451eaf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.shared.model
+
+/** Enumerates all known modes of operation of the shade. */
+sealed interface ShadeMode {
+
+ /**
+ * The single or "accordion" shade where the QS and notification parts are in two vertically
+ * stacked panels and the user can swipe up and down to expand or collapse between the two
+ * parts.
+ */
+ data object Single : ShadeMode
+
+ /**
+ * The split shade where, on large screens and unfolded foldables, the QS and notification parts
+ * are placed side-by-side and expand/collapse as a single panel.
+ */
+ data object Split : ShadeMode
+
+ /**
+ * The dual shade where the QS and notification parts each have their own independently
+ * expandable/collapsible panel on either side of the large screen / unfolded device or sharing
+ * a space on a small screen or folded device.
+ */
+ data object Dual : ShadeMode
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 8084a6f..ea549f2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -33,6 +33,7 @@
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
@@ -64,12 +65,12 @@
combine(
deviceEntryInteractor.isUnlocked,
deviceEntryInteractor.canSwipeToEnter,
- shadeInteractor.isSplitShade,
- ) { isUnlocked, canSwipeToDismiss, isSplitShade ->
+ shadeInteractor.shadeMode,
+ ) { isUnlocked, canSwipeToDismiss, shadeMode ->
destinationScenes(
isUnlocked = isUnlocked,
canSwipeToDismiss = canSwipeToDismiss,
- isSplitShade = isSplitShade,
+ shadeMode = shadeMode,
)
}
.stateIn(
@@ -79,7 +80,7 @@
destinationScenes(
isUnlocked = deviceEntryInteractor.isUnlocked.value,
canSwipeToDismiss = deviceEntryInteractor.canSwipeToEnter.value,
- isSplitShade = shadeInteractor.isSplitShade.value,
+ shadeMode = shadeInteractor.shadeMode.value,
),
)
@@ -96,8 +97,7 @@
initialValue = false
)
- /** Whether the current configuration requires the split shade to be shown. */
- val isSplitShade: StateFlow<Boolean> = shadeInteractor.isSplitShade
+ val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode
/** Notifies that some content in the shade was clicked. */
fun onContentClicked() = deviceEntryInteractor.attemptDeviceEntry()
@@ -119,7 +119,7 @@
private fun destinationScenes(
isUnlocked: Boolean,
canSwipeToDismiss: Boolean?,
- isSplitShade: Boolean,
+ shadeMode: ShadeMode,
): Map<UserAction, UserActionResult> {
val up =
when {
@@ -128,7 +128,7 @@
else -> Scenes.Lockscreen
}
- val down = if (isSplitShade) null else Scenes.QuickSettings
+ val down = Scenes.QuickSettings.takeIf { shadeMode is ShadeMode.Single }
return buildMap {
this[Swipe(SwipeDirection.Up)] = UserActionResult(up)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 6155348..5171a5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -39,8 +39,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.animation.ShadeInterpolation;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.flags.RefactorFlag;
import com.android.systemui.res.R;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
@@ -95,8 +93,6 @@
private float mCornerAnimationDistance;
private float mActualWidth = -1;
private int mMaxIconsOnLockscreen;
- private final RefactorFlag mSensitiveRevealAnim =
- RefactorFlag.forView(Flags.SENSITIVE_REVEAL_ANIM);
private boolean mCanModifyColorOfNotifications;
private boolean mCanInteract;
private NotificationStackScrollLayout mHostLayout;
@@ -266,7 +262,7 @@
}
final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight();
- if (mSensitiveRevealAnim.isEnabled() && viewState.hidden) {
+ if (viewState.hidden) {
// if the shelf is hidden, position it at the end of the stack (plus the clip
// padding), such that when it appears animated, it will smoothly move in from the
// bottom, without jump cutting any notifications
@@ -398,10 +394,6 @@
// find the first view that doesn't overlap with the shelf
int notGoneIndex = 0;
int colorOfViewBeforeLast = NO_COLOR;
- boolean backgroundForceHidden = false;
- if (mHideBackground && !((ShelfState) getViewState()).hasItemsInStableShelf) {
- backgroundForceHidden = true;
- }
int colorTwoBefore = NO_COLOR;
int previousColor = NO_COLOR;
float transitionAmount = 0.0f;
@@ -429,8 +421,7 @@
expandingAnimated, isLastChild, shelfClipStart);
// TODO(b/172289889) scale mPaddingBetweenElements with expansion amount
- if ((!mSensitiveRevealAnim.isEnabled() && ((isLastChild && !child.isInShelf())
- || backgroundForceHidden)) || aboveShelf) {
+ if (aboveShelf) {
notificationClipEnd = shelfStart + getIntrinsicHeight();
} else {
notificationClipEnd = shelfStart - mPaddingBetweenElements;
@@ -440,8 +431,7 @@
// If the current row is an ExpandableNotificationRow, update its color, roundedness,
// and icon state.
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow expandableRow = (ExpandableNotificationRow) child;
+ if (child instanceof ExpandableNotificationRow expandableRow) {
numViewsInShelf += inShelfAmount;
int ownColorUntinted = expandableRow.getBackgroundColorWithoutTint();
if (viewStart >= shelfStart && mNotGoneIndex == -1) {
@@ -471,16 +461,8 @@
notGoneIndex++;
}
- if (child instanceof ActivatableNotificationView) {
- ActivatableNotificationView anv = (ActivatableNotificationView) child;
- // Because we show whole notifications on the lockscreen, the bottom notification is
- // always "just about to enter the shelf" by normal scrolling rules. This is fine
- // if the shelf is visible, but if the shelf is hidden, it causes incorrect curling.
- // notificationClipEnd handles the discrepancy between a visible and hidden shelf,
- // so we use that when on the keyguard (and while animating away) to reduce curling.
- final float keyguardSafeShelfStart = !mSensitiveRevealAnim.isEnabled()
- && mAmbientState.isOnKeyguard() ? notificationClipEnd : shelfStart;
- updateCornerRoundnessOnScroll(anv, viewStart, keyguardSafeShelfStart);
+ if (child instanceof ActivatableNotificationView anv) {
+ updateCornerRoundnessOnScroll(anv, viewStart, shelfStart);
}
}
@@ -519,11 +501,10 @@
mShelfIcons.applyIconStates();
for (int i = 0; i < getHostLayoutChildCount(); i++) {
View child = getHostLayoutChildAt(i);
- if (!(child instanceof ExpandableNotificationRow)
+ if (!(child instanceof ExpandableNotificationRow row)
|| child.getVisibility() == GONE) {
continue;
}
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
updateContinuousClipping(row);
}
boolean hideBackground = isHidden;
@@ -613,8 +594,7 @@
private void clipTransientViews() {
for (int i = 0; i < getHostLayoutTransientViewCount(); i++) {
View transientView = getHostLayoutTransientView(i);
- if (transientView instanceof ExpandableView) {
- ExpandableView transientExpandableView = (ExpandableView) transientView;
+ if (transientView instanceof ExpandableView transientExpandableView) {
updateNotificationClipHeight(transientExpandableView, getTranslationY(), -1);
}
}
@@ -871,10 +851,9 @@
}
private void setIconTransformationAmount(ExpandableView view, float transitionAmount) {
- if (!(view instanceof ExpandableNotificationRow)) {
+ if (!(view instanceof ExpandableNotificationRow row)) {
return;
}
- ExpandableNotificationRow row = (ExpandableNotificationRow) view;
StatusBarIconView icon = row.getShelfIcon();
NotificationIconContainer.IconState iconState = getIconState(icon);
if (iconState == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
index 072f56d..dcfccd8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
@@ -61,7 +61,7 @@
return false;
}
- if (!Flags.notificationsBackgroundMediaIcons()) {
+ if (!Flags.notificationsBackgroundIcons()) {
inflateOrUpdateIcons(entry);
}
@@ -73,14 +73,14 @@
@Override
public void onEntryInit(@NonNull NotificationEntry entry) {
// We default to STATE_ICONS_UNINFLATED anyway, so there's no need to initialize it.
- if (!Flags.notificationsBackgroundMediaIcons()) {
+ if (!Flags.notificationsBackgroundIcons()) {
mIconsState.put(entry, STATE_ICONS_UNINFLATED);
}
}
@Override
public void onEntryAdded(@NonNull NotificationEntry entry) {
- if (Flags.notificationsBackgroundMediaIcons()) {
+ if (Flags.notificationsBackgroundIcons()) {
if (isMediaNotification(entry.getSbn())) {
inflateOrUpdateIcons(entry);
}
@@ -94,7 +94,7 @@
mIconsState.put(entry, STATE_ICONS_UNINFLATED);
}
- if (Flags.notificationsBackgroundMediaIcons()) {
+ if (Flags.notificationsBackgroundIcons()) {
if (isMediaNotification(entry.getSbn())) {
inflateOrUpdateIcons(entry);
}
@@ -120,7 +120,7 @@
break;
case STATE_ICONS_INFLATED:
try {
- mIconManager.updateIcons(entry);
+ mIconManager.updateIcons(entry, /* usingCache = */ false);
} catch (InflationException e) {
reportInflationError(entry, e);
mIconsState.put(entry, STATE_ICONS_ERROR);
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 6400ff6..4bbe035 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
@@ -138,7 +138,7 @@
if (entry.rowExists()) {
mLogger.logUpdatingRow(entry, params);
- mIconManager.updateIcons(entry);
+ mIconManager.updateIcons(entry, /* usingCache = */ false);
ExpandableNotificationRow row = entry.getRow();
row.reset();
updateRow(entry, row);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index a5f42bb..a900e45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -28,14 +28,24 @@
import android.widget.ImageView
import com.android.app.tracing.traceSection
import com.android.internal.statusbar.StatusBarIcon
+import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
+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.res.R
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.InflationException
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
/**
* Inflates and updates icons associated with notifications
@@ -53,9 +63,18 @@
constructor(
private val notifCollection: CommonNotifCollection,
private val launcherApps: LauncherApps,
- private val iconBuilder: IconBuilder
+ private val iconBuilder: IconBuilder,
+ @Application private val applicationCoroutineScope: CoroutineScope,
+ @Background private val bgCoroutineContext: CoroutineContext,
+ @Main private val mainCoroutineContext: CoroutineContext,
) : ConversationIconManager {
private var unimportantConversationKeys: Set<String> = emptySet()
+ /**
+ * A map of running jobs for fetching the person avatar from launcher. The key is the
+ * notification entry key.
+ */
+ private var launcherPeopleAvatarIconJobs: ConcurrentHashMap<String, Job> =
+ ConcurrentHashMap<String, Job>()
fun attach() {
notifCollection.addCollectionListener(entryListener)
@@ -136,13 +155,23 @@
* @throws InflationException Exception if required icons are not valid or specified
*/
@Throws(InflationException::class)
- fun updateIcons(entry: NotificationEntry) =
+ fun updateIcons(entry: NotificationEntry, usingCache: Boolean = false) =
traceSection("IconManager.updateIcons") {
if (!entry.icons.areIconsAvailable) {
return@traceSection
}
- entry.icons.smallIconDescriptor = null
- entry.icons.peopleAvatarDescriptor = null
+
+ if (usingCache && !Flags.notificationsBackgroundIcons()) {
+ Log.wtf(
+ TAG,
+ "Updating using the cache is not supported when the " +
+ "notifications_background_conversation_icons flag is off"
+ )
+ }
+ if (!usingCache || !Flags.notificationsBackgroundIcons()) {
+ entry.icons.smallIconDescriptor = null
+ entry.icons.peopleAvatarDescriptor = null
+ }
val (normalIconDescriptor, sensitiveIconDescriptor) = getIconDescriptors(entry)
val notificationContentDescription =
@@ -188,7 +217,7 @@
@Throws(InflationException::class)
private fun getIconDescriptor(entry: NotificationEntry, redact: Boolean): StatusBarIcon {
val n = entry.sbn.notification
- val showPeopleAvatar = isImportantConversation(entry) && !redact
+ val showPeopleAvatar = !redact && isImportantConversation(entry)
val peopleAvatarDescriptor = entry.icons.peopleAvatarDescriptor
val smallIconDescriptor = entry.icons.smallIconDescriptor
@@ -208,26 +237,18 @@
})
?: throw InflationException("No icon in notification from " + entry.sbn.packageName)
- val ic =
- StatusBarIcon(
- entry.sbn.user,
- entry.sbn.packageName,
- icon,
- n.iconLevel,
- n.number,
- iconBuilder.getIconContentDescription(n)
- )
+ val sbi = icon.toStatusBarIcon(entry)
// Cache if important conversation.
if (isImportantConversation(entry)) {
if (showPeopleAvatar) {
- entry.icons.peopleAvatarDescriptor = ic
+ entry.icons.peopleAvatarDescriptor = sbi
} else {
- entry.icons.smallIconDescriptor = ic
+ entry.icons.smallIconDescriptor = sbi
}
}
- return ic
+ return sbi
}
@Throws(InflationException::class)
@@ -243,16 +264,69 @@
}
}
- @Throws(InflationException::class)
- private fun createPeopleAvatar(entry: NotificationEntry): Icon? {
- var ic: Icon? = null
+ private fun Icon.toStatusBarIcon(entry: NotificationEntry): StatusBarIcon {
+ val n = entry.sbn.notification
+ return StatusBarIcon(
+ entry.sbn.user,
+ entry.sbn.packageName,
+ /* icon = */ this,
+ n.iconLevel,
+ n.number,
+ iconBuilder.getIconContentDescription(n)
+ )
+ }
- val shortcut = entry.ranking.conversationShortcutInfo
- if (shortcut != null) {
- ic = launcherApps.getShortcutIcon(shortcut)
+ private suspend fun getLauncherShortcutIconForPeopleAvatar(entry: NotificationEntry) =
+ withContext(bgCoroutineContext) {
+ var icon: Icon? = null
+ val shortcut = entry.ranking.conversationShortcutInfo
+ if (shortcut != null) {
+ try {
+ icon = launcherApps.getShortcutIcon(shortcut)
+ } catch (e: Exception) {
+ Log.e(
+ TAG,
+ "Error calling LauncherApps#getShortcutIcon for notification $entry: $e"
+ )
+ }
+ }
+
+ // Once we have the icon, updating it should happen on the main thread.
+ if (icon != null) {
+ withContext(mainCoroutineContext) {
+ val iconDescriptor = icon.toStatusBarIcon(entry)
+
+ // Cache the value
+ entry.icons.peopleAvatarDescriptor = iconDescriptor
+
+ // Update the icons using the cached value
+ updateIcons(entry = entry, usingCache = true)
+ }
+ }
}
- // Fall back to extract from message
+ @Throws(InflationException::class)
+ private fun createPeopleAvatar(entry: NotificationEntry): Icon {
+ var ic: Icon? = null
+
+ if (Flags.notificationsBackgroundIcons()) {
+ // Ideally we want to get the icon from launcher, but this is a binder transaction that
+ // may take longer so let's kick it off on a background thread and use a placeholder in
+ // the meantime.
+ // Cancel the previous job if necessary.
+ launcherPeopleAvatarIconJobs[entry.key]?.cancel()
+ launcherPeopleAvatarIconJobs[entry.key] =
+ applicationCoroutineScope
+ .launch { getLauncherShortcutIconForPeopleAvatar(entry) }
+ .apply { invokeOnCompletion { launcherPeopleAvatarIconJobs.remove(entry.key) } }
+ } else {
+ val shortcut = entry.ranking.conversationShortcutInfo
+ if (shortcut != null) {
+ ic = launcherApps.getShortcutIcon(shortcut)
+ }
+ }
+
+ // Try to extract from message
if (ic == null) {
val extras: Bundle = entry.sbn.notification.extras
val messages =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 5f3a83a..c05c3c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -278,8 +278,6 @@
private OnExpandClickListener mOnExpandClickListener;
private View.OnClickListener mOnFeedbackClickListener;
private Path mExpandingClipPath;
- private final RefactorFlag mInlineReplyAnimation =
- RefactorFlag.forView(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION);
private static boolean shouldSimulateSlowMeasure() {
return Compile.IS_DEBUG && RefactorFlag.forView(
@@ -355,7 +353,7 @@
nowExpanded = !isExpanded();
setUserExpanded(nowExpanded);
}
- notifyHeightChanged(true);
+ notifyHeightChanged(/* needsAnimation= */ true);
mOnExpandClickListener.onExpandClicked(mEntry, v, nowExpanded);
mMetricsLogger.action(MetricsEvent.ACTION_NOTIFICATION_EXPANDER, nowExpanded);
}
@@ -778,7 +776,7 @@
mChildrenContainer.updateGroupOverflow();
}
if (intrinsicBefore != getIntrinsicHeight()) {
- notifyHeightChanged(false /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ false);
}
if (isHeadsUp) {
mMustStayOnScreen = true;
@@ -826,7 +824,7 @@
if (mChildrenContainer != null) {
mChildrenContainer.setHeaderVisibleAmount(headerVisibleAmount);
}
- notifyHeightChanged(false /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ false);
}
}
@@ -1088,7 +1086,7 @@
boolean wasAboveShelf = isAboveShelf();
mIsPinned = pinned;
if (intrinsicHeight != getIntrinsicHeight()) {
- notifyHeightChanged(false /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ false);
}
if (pinned) {
setAnimationRunning(true);
@@ -2611,7 +2609,7 @@
onExpansionChanged(true /* userAction */, wasExpanded);
if (!wasExpanded && isExpanded()
&& getActualHeight() != getIntrinsicHeight()) {
- notifyHeightChanged(true /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ true);
}
}
@@ -2623,7 +2621,7 @@
if (mIsSummaryWithChildren) {
mChildrenContainer.onExpansionChanged();
}
- notifyHeightChanged(false /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ false);
}
updateShelfIconColor();
}
@@ -2661,7 +2659,7 @@
if (expand != mIsSystemExpanded) {
final boolean wasExpanded = isExpanded();
mIsSystemExpanded = expand;
- notifyHeightChanged(false /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ false);
onExpansionChanged(false /* userAction */, wasExpanded);
if (mIsSummaryWithChildren) {
mChildrenContainer.updateGroupOverflow();
@@ -2680,7 +2678,7 @@
if (mIsSummaryWithChildren) {
mChildrenContainer.updateGroupOverflow();
}
- notifyHeightChanged(false /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ false);
}
if (isAboveShelf() != wasAboveShelf) {
mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
@@ -2837,7 +2835,7 @@
super.onLayout(changed, left, top, right, bottom);
if (intrinsicBefore != getIntrinsicHeight()
&& (intrinsicBefore != 0 || getActualHeight() > 0)) {
- notifyHeightChanged(true /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ true);
}
if (mMenuRow != null && mMenuRow.getMenuView() != null) {
mMenuRow.onParentHeightUpdate();
@@ -2880,8 +2878,7 @@
mSensitiveHiddenInGeneral = hideSensitive;
int intrinsicAfter = getIntrinsicHeight();
if (intrinsicBefore != intrinsicAfter) {
- boolean needsAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
- notifyHeightChanged(needsAnimation);
+ notifyHeightChanged(/* needsAnimation= */ true);
}
}
@@ -3018,7 +3015,7 @@
if (isChildInGroup()) {
mGroupExpansionManager.setGroupExpanded(mEntry, true);
}
- notifyHeightChanged(false /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ false);
}
public void setChildrenExpanded(boolean expanded, boolean animate) {
@@ -3241,13 +3238,8 @@
mGuts.setActualHeight(height);
return;
}
- int contentHeight = Math.max(getMinHeight(), height);
for (NotificationContentView l : mLayouts) {
- if (mInlineReplyAnimation.isEnabled()) {
- l.setContentHeight(height);
- } else {
- l.setContentHeight(contentHeight);
- }
+ l.setContentHeight(height);
}
if (mIsSummaryWithChildren) {
mChildrenContainer.setActualHeight(height);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 137e1b2..8a3e7e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -699,8 +699,7 @@
int hint;
if (mHeadsUpChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_HEADSUP)) {
hint = getViewHeight(VISIBLE_TYPE_HEADSUP);
- if (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isAnimatingAppearance()
- && mHeadsUpRemoteInputController.isFocusAnimationFlagActive()) {
+ if (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isAnimatingAppearance()) {
// While the RemoteInputView is animating its appearance, it should be allowed
// to overlap the hint, therefore no space is reserved for the hint during the
// appearance animation of the RemoteInputView
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 b205071..77e9425 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
@@ -87,7 +87,6 @@
import com.android.systemui.ExpandHelper;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
-import com.android.systemui.flags.RefactorFlag;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.res.R;
@@ -197,8 +196,6 @@
*/
private Set<Integer> mDebugTextUsedYPositions;
private final boolean mDebugRemoveAnimation;
- private final boolean mSensitiveRevealAnimEndabled;
- private final RefactorFlag mAnimatedInsets;
private int mContentHeight;
private float mIntrinsicContentHeight;
private int mPaddingBetweenElements;
@@ -619,9 +616,6 @@
Flags.LOCKSCREEN_ENABLE_LANDSCAPE);
mDebugLines = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_LINES);
mDebugRemoveAnimation = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
- mSensitiveRevealAnimEndabled = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
- mAnimatedInsets =
- new RefactorFlag(mFeatureFlags, Flags.ANIMATED_NOTIFICATION_SHADE_INSETS);
mSectionsManager = Dependency.get(NotificationSectionsManager.class);
mScreenOffAnimationController =
Dependency.get(ScreenOffAnimationController.class);
@@ -656,9 +650,7 @@
mGroupMembershipManager = Dependency.get(GroupMembershipManager.class);
mGroupExpansionManager = Dependency.get(GroupExpansionManager.class);
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
- if (mAnimatedInsets.isEnabled()) {
- setWindowInsetsAnimationCallback(mInsetsCallback);
- }
+ setWindowInsetsAnimationCallback(mInsetsCallback);
}
/**
@@ -1734,11 +1726,7 @@
return;
}
mForcedScroll = v;
- if (mAnimatedInsets.isEnabled()) {
- updateForcedScroll();
- } else {
- scrollTo(v);
- }
+ updateForcedScroll();
}
public boolean scrollTo(View v) {
@@ -1783,31 +1771,15 @@
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- if (!mAnimatedInsets.isEnabled()) {
- mBottomInset = insets.getInsets(WindowInsets.Type.ime()).bottom;
- }
mWaterfallTopInset = 0;
final DisplayCutout cutout = insets.getDisplayCutout();
if (cutout != null) {
mWaterfallTopInset = cutout.getWaterfallInsets().top;
}
- if (mAnimatedInsets.isEnabled() && !mIsInsetAnimationRunning) {
+ if (!mIsInsetAnimationRunning) {
// update bottom inset e.g. after rotation
updateBottomInset(insets);
}
- if (!mAnimatedInsets.isEnabled()) {
- int range = getScrollRange();
- if (mOwnScrollY > range) {
- // HACK: We're repeatedly getting staggered insets here while the IME is
- // animating away. To work around that we'll wait until things have settled.
- removeCallbacks(mReclamp);
- postDelayed(mReclamp, 50);
- } else if (mForcedScroll != null) {
- // The scroll was requested before we got the actual inset - in case we need
- // to scroll up some more do so now.
- scrollTo(mForcedScroll);
- }
- }
return insets;
}
@@ -2576,7 +2548,7 @@
return;
}
child.setOnHeightChangedListener(null);
- if (child instanceof ExpandableNotificationRow && mSensitiveRevealAnimEndabled) {
+ if (child instanceof ExpandableNotificationRow) {
NotificationEntry entry = ((ExpandableNotificationRow) child).getEntry();
entry.removeOnSensitivityChangedListener(mOnChildSensitivityChangedListener);
}
@@ -2872,7 +2844,7 @@
private void onViewAddedInternal(ExpandableView child) {
updateHideSensitiveForChild(child);
child.setOnHeightChangedListener(mOnChildHeightChangedListener);
- if (child instanceof ExpandableNotificationRow && mSensitiveRevealAnimEndabled) {
+ if (child instanceof ExpandableNotificationRow) {
NotificationEntry entry = ((ExpandableNotificationRow) child).getEntry();
entry.addOnSensitivityChangedListener(mOnChildSensitivityChangedListener);
}
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 84b39c1..5e87a7a 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
@@ -20,7 +20,6 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
import com.android.systemui.common.shared.model.NotificationContainerBounds
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
@@ -37,8 +36,6 @@
import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE
import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
-import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
-import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
@@ -97,7 +94,6 @@
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val shadeInteractor: ShadeInteractor,
- communalInteractor: CommunalInteractor,
private val alternateBouncerToGoneTransitionViewModel:
AlternateBouncerToGoneTransitionViewModel,
private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
@@ -127,22 +123,6 @@
private val statesForConstrainedNotifications: Set<KeyguardState> =
setOf(AOD, LOCKSCREEN, DOZING, ALTERNATE_BOUNCER, PRIMARY_BOUNCER)
- private val lockscreenToGlanceableHubRunning =
- keyguardTransitionInteractor
- .transition(LOCKSCREEN, GLANCEABLE_HUB)
- .map { it.transitionState == STARTED || it.transitionState == RUNNING }
- .distinctUntilChanged()
- .onStart { emit(false) }
- .dumpWhileCollecting("lockscreenToGlanceableHubRunning")
-
- private val glanceableHubToLockscreenRunning =
- keyguardTransitionInteractor
- .transition(GLANCEABLE_HUB, LOCKSCREEN)
- .map { it.transitionState == STARTED || it.transitionState == RUNNING }
- .distinctUntilChanged()
- .onStart { emit(false) }
- .dumpWhileCollecting("glanceableHubToLockscreenRunning")
-
/**
* Shade locked is a legacy concept, but necessary to mimic current functionality. Listen for
* both SHADE_LOCKED and shade/qs expansion in order to determine lock state, as one can arrive
@@ -218,21 +198,38 @@
)
.dumpValue("isOnLockscreenWithoutShade")
+ /** If the user is visually on the glanceable hub or transitioning to/from it */
+ private val isOnGlanceableHub: Flow<Boolean> =
+ combine(
+ keyguardTransitionInteractor.finishedKeyguardState.map { state ->
+ state == GLANCEABLE_HUB
+ },
+ keyguardTransitionInteractor
+ .isInTransitionWhere { from, to ->
+ from == GLANCEABLE_HUB || to == GLANCEABLE_HUB
+ }
+ .onStart { emit(false) }
+ ) { isOnGlanceableHub, transitioningToOrFromHub ->
+ isOnGlanceableHub || transitioningToOrFromHub
+ }
+ .distinctUntilChanged()
+ .dumpWhileCollecting("isOnGlanceableHub")
+
/** Are we purely on the glanceable hub without the shade/qs? */
val isOnGlanceableHubWithoutShade: Flow<Boolean> =
combine(
- communalInteractor.isIdleOnCommunal,
+ isOnGlanceableHub,
// Shade with notifications
shadeInteractor.shadeExpansion.map { it > 0f },
// Shade without notifications, quick settings only (pull down from very top on
// lockscreen)
shadeInteractor.qsExpansion.map { it > 0f },
- ) { isIdleOnCommunal, isShadeVisible, qsExpansion ->
- isIdleOnCommunal && !(isShadeVisible || qsExpansion)
+ ) { isGlanceableHub, isShadeVisible, qsExpansion ->
+ isGlanceableHub && !(isShadeVisible || qsExpansion)
}
.stateIn(
scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
+ started = SharingStarted.Eagerly,
initialValue = false,
)
.dumpWhileCollecting("isOnGlanceableHubWithoutShade")
@@ -353,6 +350,9 @@
if (shadeExpansion > 0f || qsExpansion > 0f) {
if (configurationBasedDimensions.useSplitShade) {
emit(1f)
+ } else if (qsExpansion == 1f) {
+ // Ensure HUNs will be visible in QS shade (at least while unlocked)
+ emit(1f)
} else {
// Fade as QS shade expands
emit(1f - qsExpansion)
@@ -431,39 +431,35 @@
}
/**
- * Returns a flow of the expected alpha while running a LOCKSCREEN<->GLANCEABLE_HUB transition
- * or idle on the glanceable hub.
+ * Returns a flow of the expected alpha while running a LOCKSCREEN<->GLANCEABLE_HUB or
+ * DREAMING<->GLANCEABLE_HUB transition or idle on the hub.
*
* Must return 1.0f when not controlling the alpha since notifications does a min of all the
* alpha sources.
*/
val glanceableHubAlpha: Flow<Float> =
- isOnGlanceableHubWithoutShade
- .flatMapLatest { isOnGlanceableHubWithoutShade ->
- combineTransform(
- lockscreenToGlanceableHubRunning,
- glanceableHubToLockscreenRunning,
- merge(
- lockscreenToGlanceableHubTransitionViewModel.notificationAlpha,
- glanceableHubToLockscreenTransitionViewModel.notificationAlpha,
- )
- ) { lockscreenToGlanceableHubRunning, glanceableHubToLockscreenRunning, alpha ->
- if (isOnGlanceableHubWithoutShade) {
- // Notifications should not be visible on the glanceable hub.
- // TODO(b/321075734): implement a way to actually set the notifications to
- // gone
- // while on the hub instead of just adjusting alpha
- emit(0f)
- } else if (
- lockscreenToGlanceableHubRunning || glanceableHubToLockscreenRunning
- ) {
- emit(alpha)
- } else {
- // Not on the hub and no transitions running, return full visibility so we
- // don't
- // block the notifications from showing.
- emit(1f)
- }
+ combineTransform(
+ isOnGlanceableHubWithoutShade,
+ isOnLockscreen,
+ merge(
+ lockscreenToGlanceableHubTransitionViewModel.notificationAlpha,
+ glanceableHubToLockscreenTransitionViewModel.notificationAlpha,
+ )
+ ) { isOnGlanceableHubWithoutShade, isOnLockscreen, alpha,
+ ->
+ if (isOnGlanceableHubWithoutShade && !isOnLockscreen) {
+ // Notifications should not be visible on the glanceable hub.
+ // TODO(b/321075734): implement a way to actually set the notifications to
+ // gone while on the hub instead of just adjusting alpha
+ emit(0f)
+ } else if (isOnGlanceableHubWithoutShade) {
+ // We are transitioning between hub and lockscreen, so set the alpha for the
+ // transition animation.
+ emit(alpha)
+ } else {
+ // Not on the hub and no transitions running, return full visibility so we
+ // don't block the notifications from showing.
+ emit(1f)
}
}
.dumpWhileCollecting("glanceableHubAlpha")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 23a080b..a55de25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -648,6 +648,10 @@
}
if (intent.isActivity) {
assistManagerLazy.get().hideAssist()
+ // This activity could have started while the device is dreaming, in which case
+ // the dream would occlude the activity. In order to show the newly started
+ // activity, we wake from the dream.
+ keyguardUpdateMonitor.awakenFromDream()
}
intentSentUiThreadCallback?.let { postOnUiThread(runnable = it) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index e2e13a1..ab9ecab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -313,7 +313,7 @@
mHeadsUpManager.unpinAll(true /* userUnpinned */);
mMetricsLogger.count("panel_open", 1);
} else if (!mQsController.getExpanded()
- && !mShadeViewController.isExpandingOrCollapsing()) {
+ && !mShadeController.isExpandingOrCollapsing()) {
mShadeController.animateExpandQs();
mMetricsLogger.count("panel_open_qs", 1);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index a155e94..24be3db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -43,6 +43,7 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.AnimationStateHandler;
+import com.android.systemui.statusbar.policy.AvalancheController;
import com.android.systemui.statusbar.policy.BaseHeadsUpManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
@@ -124,9 +125,10 @@
AccessibilityManagerWrapper accessibilityManagerWrapper,
UiEventLogger uiEventLogger,
JavaAdapter javaAdapter,
- ShadeInteractor shadeInteractor) {
+ ShadeInteractor shadeInteractor,
+ AvalancheController avalancheController) {
super(context, logger, handler, globalSettings, systemClock, executor,
- accessibilityManagerWrapper, uiEventLogger);
+ accessibilityManagerWrapper, uiEventLogger, avalancheController);
Resources resources = mContext.getResources();
mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time);
statusBarStateController.addCallback(mStatusBarStateListener);
@@ -279,7 +281,7 @@
if (headsUpEntry != null && headsUpEntry.mRemoteInputActive != remoteInputActive) {
headsUpEntry.mRemoteInputActive = remoteInputActive;
if (remoteInputActive) {
- headsUpEntry.removeAutoRemovalCallbacks("setRemoteInputActive(true)");
+ headsUpEntry.cancelAutoRemovalCallbacks("setRemoteInputActive(true)");
} else {
headsUpEntry.updateEntry(false /* updatePostTime */, "setRemoteInputActive(false)");
}
@@ -482,7 +484,7 @@
this.mExpanded = expanded;
if (expanded) {
- removeAutoRemovalCallbacks("setExpanded(true)");
+ cancelAutoRemovalCallbacks("setExpanded(true)");
} else {
updateEntry(false /* updatePostTime */, "setExpanded(false)");
}
@@ -495,7 +497,7 @@
mGutsShownPinned = gutsShownPinned;
if (gutsShownPinned) {
- removeAutoRemovalCallbacks("setGutsShownPinned(true)");
+ cancelAutoRemovalCallbacks("setGutsShownPinned(true)");
} else {
updateEntry(false /* updatePostTime */, "setGutsShownPinned(false)");
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index d4960d7..712f65d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -328,7 +328,14 @@
GLANCEABLE_HUB_OVER_DREAM {
@Override
public void prepare(ScrimState previousState) {
- GLANCEABLE_HUB.prepare(previousState);
+ // No scrims should be visible by default in this state.
+ mBehindAlpha = 0;
+ mNotifAlpha = 0;
+ mFrontAlpha = 0;
+
+ mFrontTint = Color.TRANSPARENT;
+ mBehindTint = mBackgroundColor;
+ mNotifTint = mClipQsScrim ? mBackgroundColor : Color.TRANSPARENT;
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index ba89d4a..7dd328a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -579,7 +579,7 @@
final boolean hideBouncerOverDream =
mDreamOverlayStateController.isOverlayActive()
&& (mShadeLockscreenInteractor.isExpanded()
- || mShadeLockscreenInteractor.isExpandingOrCollapsing());
+ || mShadeController.get().isExpandingOrCollapsing());
final boolean isUserTrackingStarted =
event.getFraction() != EXPANSION_HIDDEN && event.getTracking();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 8e9c038..5b14259 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -44,6 +44,7 @@
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -87,6 +88,7 @@
private final NotificationMediaManager mMediaManager;
private final NotificationGutsManager mGutsManager;
private final ShadeViewController mNotificationPanel;
+ private final PanelExpansionInteractor mPanelExpansionInteractor;
private final HeadsUpManager mHeadsUpManager;
private final AboveShelfObserver mAboveShelfObserver;
private final DozeScrimController mDozeScrimController;
@@ -108,6 +110,7 @@
StatusBarNotificationPresenter(
Context context,
ShadeViewController panel,
+ PanelExpansionInteractor panelExpansionInteractor,
QuickSettingsController quickSettingsController,
HeadsUpManager headsUp,
NotificationShadeWindowView statusBarWindow,
@@ -134,6 +137,7 @@
mActivityStarter = activityStarter;
mKeyguardStateController = keyguardStateController;
mNotificationPanel = panel;
+ mPanelExpansionInteractor = panelExpansionInteractor;
mQsController = quickSettingsController;
mHeadsUpManager = headsUp;
mDynamicPrivacyController = dynamicPrivacyController;
@@ -202,7 +206,7 @@
@Override
public boolean isCollapsing() {
- return mNotificationPanel.isCollapsing()
+ return mPanelExpansionInteractor.isCollapsing()
|| mNotificationShadeWindowController.isLaunchingActivity();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
new file mode 100644
index 0000000..6aaf5d6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.policy
+
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
+import com.android.systemui.statusbar.policy.BaseHeadsUpManager.HeadsUpEntry
+import javax.inject.Inject
+
+/*
+ * Control when heads up notifications show during an avalanche where notifications arrive in fast
+ * succession, by delaying visual listener side effects and removal handling from BaseHeadsUpManager
+ */
+@SysUISingleton
+class AvalancheController @Inject constructor() {
+
+ private val tag = "AvalancheController"
+ private val debug = false
+
+ // HUN showing right now, in the floating state where full shade is hidden, on launcher or AOD
+ @VisibleForTesting var headsUpEntryShowing: HeadsUpEntry? = null
+
+ // List of runnables to run for the HUN showing right now
+ private var headsUpEntryShowingRunnableList: MutableList<Runnable> = ArrayList()
+
+ // HeadsUpEntry waiting to show
+ // Use sortable list instead of priority queue for debugging
+ private val nextList: MutableList<HeadsUpEntry> = ArrayList()
+
+ // Map of HeadsUpEntry waiting to show, and runnables to run when it shows.
+ // Use HashMap instead of SortedMap for faster lookup, and also because the ordering
+ // provided by HeadsUpEntry.compareTo is not consistent over time or with HeadsUpEntry.equals
+ @VisibleForTesting var nextMap: MutableMap<HeadsUpEntry, MutableList<Runnable>> = HashMap()
+
+ // Map of Runnable to label for debugging only
+ private val debugRunnableLabelMap: MutableMap<Runnable, String> = HashMap()
+
+ // HeadsUpEntry we did not show at all because they are not the top priority hun in their batch
+ // For debugging only
+ @VisibleForTesting var debugDropSet: MutableSet<HeadsUpEntry> = HashSet()
+
+ /**
+ * Run or delay Runnable for given HeadsUpEntry
+ */
+ fun update(entry: HeadsUpEntry, runnable: Runnable, label: String) {
+ if (!NotificationThrottleHun.isEnabled) {
+ runnable.run()
+ return
+ }
+ val fn = "[$label] => AvalancheController.update ${getKey(entry)}"
+
+ if (debug) {
+ debugRunnableLabelMap[runnable] = label
+ }
+
+ if (isShowing(entry)) {
+ log {"$fn => [update showing]" }
+ runnable.run()
+ } else if (entry in nextMap) {
+ log { "$fn => [update next]" }
+ nextMap[entry]?.add(runnable)
+ } else if (headsUpEntryShowing == null) {
+ log { "$fn => [showNow]" }
+ showNow(entry, arrayListOf(runnable))
+ } else {
+ // Clean up invalid state when entry is in list but not map and vice versa
+ if (entry in nextMap) nextMap.remove(entry)
+ if (entry in nextList) nextList.remove(entry)
+
+ addToNext(entry, runnable)
+
+ // Shorten headsUpEntryShowing display time
+ val nextIndex = nextList.indexOf(entry)
+ val isOnlyNextEntry = nextIndex == 0 && nextList.size == 1
+ if (isOnlyNextEntry) {
+ // HeadsUpEntry.updateEntry recursively calls AvalancheController#update
+ // and goes to the isShowing case above
+ headsUpEntryShowing!!.updateEntry(false, "avalanche duration update")
+ }
+ }
+ logState("after $fn")
+ }
+
+ @VisibleForTesting
+ fun addToNext(entry: HeadsUpEntry, runnable: Runnable) {
+ nextMap[entry] = arrayListOf(runnable)
+ nextList.add(entry)
+ }
+
+ /**
+ * Run or ignore Runnable for given HeadsUpEntry. If entry was never shown, ignore and delete
+ * all Runnables associated with that entry.
+ */
+ fun delete(entry: HeadsUpEntry, runnable: Runnable, label: String) {
+ if (!NotificationThrottleHun.isEnabled) {
+ runnable.run()
+ return
+ }
+ val fn = "[$label] => AvalancheController.delete " + getKey(entry)
+
+ if (entry in nextMap) {
+ log { "$fn => [remove from next]" }
+ if (entry in nextMap) nextMap.remove(entry)
+ if (entry in nextList) nextList.remove(entry)
+ } else if (entry in debugDropSet) {
+ log { "$fn => [remove from dropset]" }
+ debugDropSet.remove(entry)
+ } else if (isShowing(entry)) {
+ log { "$fn => [remove showing ${getKey(entry)}]" }
+ runnable.run()
+ showNext()
+ } else {
+ log { "$fn => [removing untracked ${getKey(entry)}]" }
+ }
+ logState("after $fn")
+ }
+
+ /**
+ * Returns true if given HeadsUpEntry is the last one tracked by AvalancheController. Used by
+ * BaseHeadsUpManager.HeadsUpEntry.calculateFinishTime to shorten display duration during active
+ * avalanche.
+ */
+ fun shortenDuration(entry: HeadsUpEntry): Boolean {
+ if (!NotificationThrottleHun.isEnabled) {
+ // Use default display duration, like we always did before AvalancheController existed
+ return false
+ }
+ val showingList: MutableList<HeadsUpEntry> = mutableListOf()
+ headsUpEntryShowing?.let { showingList.add(it) }
+ val allEntryList = showingList + nextList
+
+ // Shorten duration if not last entry
+ return allEntryList.indexOf(entry) != allEntryList.size - 1
+ }
+
+ /**
+ * Return true if entry is waiting to show.
+ */
+ fun isWaiting(key: String): Boolean {
+ if (!NotificationThrottleHun.isEnabled) {
+ return false
+ }
+ for (entry in nextMap.keys) {
+ if (entry.mEntry?.key.equals(key)) {
+ return true
+ }
+ }
+ return false
+ }
+
+ /**
+ * Return list of keys for huns waiting
+ */
+ fun getWaitingKeys(): MutableList<String> {
+ if (!NotificationThrottleHun.isEnabled) {
+ return mutableListOf()
+ }
+ val keyList = mutableListOf<String>()
+ for (entry in nextMap.keys) {
+ entry.mEntry?.let { keyList.add(entry.mEntry!!.key) }
+ }
+ return keyList
+ }
+
+ private fun isShowing(entry: HeadsUpEntry): Boolean {
+ return headsUpEntryShowing != null && entry.mEntry?.key == headsUpEntryShowing?.mEntry?.key
+ }
+
+ private fun showNow(entry: HeadsUpEntry, runnableList: MutableList<Runnable>) {
+ log { "show " + getKey(entry) + " backlog size: " + runnableList.size }
+
+ headsUpEntryShowing = entry
+
+ runnableList.forEach {
+ if (it in debugRunnableLabelMap) {
+ log { "run runnable from: ${debugRunnableLabelMap[it]}" }
+ }
+ it.run()
+ }
+ }
+
+ private fun showNext() {
+ log { "showNext" }
+ headsUpEntryShowing = null
+
+ if (nextList.isEmpty()) {
+ log { "no more to show!" }
+ return
+ }
+
+ // Only show first (top priority) entry in next batch
+ nextList.sort()
+ headsUpEntryShowing = nextList[0]
+ headsUpEntryShowingRunnableList = nextMap[headsUpEntryShowing]!!
+
+ // Remove runnable labels for dropped huns
+ val listToDrop = nextList.subList(1, nextList.size)
+ if (debug) {
+ // Clear runnable labels
+ for (e in listToDrop) {
+ val runnableList = nextMap[e]!!
+ for (r in runnableList) {
+ debugRunnableLabelMap.remove(r)
+ }
+ }
+ debugDropSet.addAll(listToDrop)
+ }
+
+ clearNext()
+ showNow(headsUpEntryShowing!!, headsUpEntryShowingRunnableList)
+ }
+
+ fun clearNext() {
+ nextList.clear()
+ nextMap.clear()
+ }
+
+ // Methods below are for logging only ==========================================================
+
+ private inline fun log(s: () -> String) {
+ if (debug) {
+ Log.d(tag, s())
+ }
+ }
+
+ // TODO(b/315362456) expose as dumpable for bugreports
+ private fun logState(reason: String) {
+ log { "state $reason" }
+ log { "showing: " + getKey(headsUpEntryShowing) }
+ log { "next list: $nextListStr map: $nextMapStr" }
+ log { "drop: $dropSetStr" }
+ }
+
+ private val dropSetStr: String
+ get() {
+ val queue = ArrayList<String>()
+ for (entry in debugDropSet) {
+ queue.add(getKey(entry))
+ }
+ return java.lang.String.join(" ", queue)
+ }
+
+ private val nextListStr: String
+ get() {
+ val queue = ArrayList<String>()
+ for (entry in nextList) {
+ queue.add(getKey(entry))
+ }
+ return java.lang.String.join(" ", queue)
+ }
+
+ private val nextMapStr: String
+ get() {
+ val queue = ArrayList<String>()
+ for (entry in nextMap.keys) {
+ queue.add(getKey(entry))
+ }
+ return java.lang.String.join(" ", queue)
+ }
+
+ fun getKey(entry: HeadsUpEntry?): String {
+ if (entry == null) {
+ return "null"
+ }
+ if (entry.mEntry == null) {
+ return entry.toString()
+ }
+ return entry.mEntry!!.key
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index 530e49c..05cc73e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -45,6 +45,8 @@
import com.android.systemui.util.time.SystemClock;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
import java.util.stream.Stream;
/**
@@ -68,6 +70,7 @@
private final AccessibilityManagerWrapper mAccessibilityMgr;
private final UiEventLogger mUiEventLogger;
+ private final AvalancheController mAvalancheController;
protected final SystemClock mSystemClock;
protected final ArrayMap<String, HeadsUpEntry> mHeadsUpEntryMap = new ArrayMap<>();
@@ -100,13 +103,15 @@
SystemClock systemClock,
@Main DelayableExecutor executor,
AccessibilityManagerWrapper accessibilityManagerWrapper,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger,
+ AvalancheController avalancheController) {
mLogger = logger;
mExecutor = executor;
mSystemClock = systemClock;
mContext = context;
mAccessibilityMgr = accessibilityManagerWrapper;
mUiEventLogger = uiEventLogger;
+ mAvalancheController = avalancheController;
Resources resources = context.getResources();
mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
mStickyForSomeTimeAutoDismissTime = resources.getInteger(
@@ -157,18 +162,26 @@
*/
@Override
public void showNotification(@NonNull NotificationEntry entry) {
- mLogger.logShowNotification(entry);
-
- // Add new entry and begin managing it
HeadsUpEntry headsUpEntry = createHeadsUpEntry();
- headsUpEntry.setEntry(entry);
- mHeadsUpEntryMap.put(entry.getKey(), headsUpEntry);
- onEntryAdded(headsUpEntry);
- entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- entry.setIsHeadsUpEntry(true);
- updateNotification(entry.getKey(), true /* shouldHeadsUpAgain */);
- entry.setInterruption();
+ // Attach NotificationEntry for AvalancheController to log key and
+ // record mPostTime for AvalancheController sorting
+ headsUpEntry.setEntry(entry);
+
+ Runnable runnable = () -> {
+ // TODO(b/315362456) log outside runnable too
+ mLogger.logShowNotification(entry);
+
+ // Add new entry and begin managing it
+ mHeadsUpEntryMap.put(entry.getKey(), headsUpEntry);
+ onEntryAdded(headsUpEntry);
+ entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ entry.setIsHeadsUpEntry(true);
+
+ updateNotificationInternal(entry.getKey(), true /* shouldHeadsUpAgain */);
+ entry.setInterruption();
+ };
+ mAvalancheController.update(headsUpEntry, runnable, "showNotification");
}
/**
@@ -181,6 +194,11 @@
@Override
public boolean removeNotification(@NonNull String key, boolean releaseImmediately) {
mLogger.logRemoveNotification(key, releaseImmediately);
+
+ if (mAvalancheController.isWaiting(key)) {
+ removeEntry(key);
+ return true;
+ }
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
if (headsUpEntry == null) {
return true;
@@ -203,6 +221,14 @@
*/
public void updateNotification(@NonNull String key, boolean shouldHeadsUpAgain) {
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+ Runnable runnable = () -> {
+ updateNotificationInternal(key, shouldHeadsUpAgain);
+ };
+ mAvalancheController.update(headsUpEntry, runnable, "updateNotification");
+ }
+
+ private void updateNotificationInternal(@NonNull String key, boolean shouldHeadsUpAgain) {
+ HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
mLogger.logUpdateNotification(key, shouldHeadsUpAgain, headsUpEntry != null);
if (headsUpEntry == null) {
// the entry was released before this update (i.e by a listener) This can happen
@@ -231,12 +257,16 @@
for (String key : keysToRemove) {
removeEntry(key);
}
+ for (String key : mAvalancheController.getWaitingKeys()) {
+ removeEntry(key);
+ }
}
/**
* Returns the entry if it is managed by this manager.
* @param key key of notification
* @return the entry
+ * TODO(b/315362456) See if caller needs to check AvalancheController waiting entries
*/
@Nullable
public NotificationEntry getEntry(@NonNull String key) {
@@ -251,6 +281,7 @@
@NonNull
@Override
public Stream<NotificationEntry> getAllEntries() {
+ // TODO(b/315362456) See if callers need to check AvalancheController
return mHeadsUpEntryMap.values().stream().map(headsUpEntry -> headsUpEntry.mEntry);
}
@@ -267,7 +298,7 @@
* @return true if the notification is managed by this manager
*/
public boolean isHeadsUpEntry(@NonNull String key) {
- return mHeadsUpEntryMap.containsKey(key);
+ return mHeadsUpEntryMap.containsKey(key) || mAvalancheController.isWaiting(key);
}
/**
@@ -331,7 +362,7 @@
* Manager-specific logic that should occur when an entry is added.
* @param headsUpEntry entry added
*/
- protected void onEntryAdded(HeadsUpEntry headsUpEntry) {
+ void onEntryAdded(HeadsUpEntry headsUpEntry) {
NotificationEntry entry = headsUpEntry.mEntry;
entry.setHeadsUp(true);
@@ -349,20 +380,24 @@
*/
protected final void removeEntry(@NonNull String key) {
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
- if (headsUpEntry == null) {
- return;
- }
- NotificationEntry entry = headsUpEntry.mEntry;
- // If the notification is animating, we will remove it at the end of the animation.
- if (entry != null && entry.isExpandAnimationRunning()) {
- return;
- }
- entry.demoteStickyHun();
- mHeadsUpEntryMap.remove(key);
- onEntryRemoved(headsUpEntry);
- entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- headsUpEntry.reset();
+ Runnable runnable = () -> {
+ if (headsUpEntry == null) {
+ return;
+ }
+ NotificationEntry entry = headsUpEntry.mEntry;
+
+ // If the notification is animating, we will remove it at the end of the animation.
+ if (entry != null && entry.isExpandAnimationRunning()) {
+ return;
+ }
+ entry.demoteStickyHun();
+ mHeadsUpEntryMap.remove(key);
+ onEntryRemoved(headsUpEntry);
+ entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ headsUpEntry.reset();
+ };
+ mAvalancheController.delete(headsUpEntry, runnable, "removeEntry");
}
/**
@@ -380,7 +415,7 @@
}
}
- protected void updatePinnedMode() {
+ private void updatePinnedMode() {
boolean hasPinnedNotification = hasPinnedNotificationInternal();
if (hasPinnedNotification == mHasPinnedNotification) {
return;
@@ -416,7 +451,9 @@
* Snoozes all current Heads Up Notifications.
*/
public void snooze() {
- for (String key : mHeadsUpEntryMap.keySet()) {
+ List<String> keySet = new ArrayList<>(mHeadsUpEntryMap.keySet());
+ keySet.addAll(mAvalancheController.getWaitingKeys());
+ for (String key : keySet) {
HeadsUpEntry entry = getHeadsUpEntry(key);
String packageName = entry.mEntry.getSbn().getPackageName();
String snoozeKey = snoozeKey(packageName, mUser);
@@ -432,6 +469,7 @@
@Nullable
protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) {
+ // TODO(b/315362456) See if callers need to check AvalancheController
return (HeadsUpEntry) mHeadsUpEntryMap.get(key);
}
@@ -515,18 +553,22 @@
*/
public void unpinAll(boolean userUnPinned) {
for (String key : mHeadsUpEntryMap.keySet()) {
- HeadsUpEntry entry = getHeadsUpEntry(key);
- setEntryPinned(entry, false /* isPinned */);
- // maybe it got un sticky
- entry.updateEntry(false /* updatePostTime */, "unpinAll");
+ HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
- // when the user unpinned all of HUNs by moving one HUN, all of HUNs should not stay
- // on the screen.
- if (userUnPinned && entry.mEntry != null) {
- if (entry.mEntry.mustStayOnScreen()) {
- entry.mEntry.setHeadsUpIsVisible();
+ Runnable runnable = () -> {
+ setEntryPinned(headsUpEntry, false /* isPinned */);
+ // maybe it got un sticky
+ headsUpEntry.updateEntry(false /* updatePostTime */, "unpinAll");
+
+ // when the user unpinned all of HUNs by moving one HUN, all of HUNs should not stay
+ // on the screen.
+ if (userUnPinned && headsUpEntry.mEntry != null) {
+ if (headsUpEntry.mEntry.mustStayOnScreen()) {
+ headsUpEntry.mEntry.setHeadsUpIsVisible();
+ }
}
- }
+ };
+ mAvalancheController.delete(headsUpEntry, runnable, "unpinAll");
}
}
@@ -606,6 +648,7 @@
*/
@Override
public boolean isSticky(String key) {
+ // TODO(b/315362456) See if callers need to check AvalancheController
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
if (headsUpEntry != null) {
return headsUpEntry.isSticky();
@@ -633,9 +676,10 @@
/**
* This represents a notification and how long it is in a heads up mode. It also manages its
- * lifecycle automatically when created.
+ * lifecycle automatically when created. This class is public because it is exposed by methods
+ * of AvalancheController that take it as param.
*/
- protected class HeadsUpEntry implements Comparable<HeadsUpEntry> {
+ public class HeadsUpEntry implements Comparable<HeadsUpEntry> {
public boolean mRemoteInputActive;
public boolean mUserActionMayIndirectlyRemove;
@@ -672,27 +716,41 @@
}
/**
+ * An interface that returns the amount of time left this HUN should show.
+ */
+ interface FinishTimeUpdater {
+ long updateAndGetTimeRemaining();
+ }
+
+ /**
* Updates an entry's removal time.
* @param updatePostTime whether or not to refresh the post time
*/
public void updateEntry(boolean updatePostTime, @Nullable String reason) {
- mLogger.logUpdateEntry(mEntry, updatePostTime, reason);
+ Runnable runnable = () -> {
+ mLogger.logUpdateEntry(mEntry, updatePostTime, reason);
- final long now = mSystemClock.elapsedRealtime();
- mEarliestRemovalTime = now + mMinimumDisplayTime;
+ final long now = mSystemClock.elapsedRealtime();
+ mEarliestRemovalTime = now + mMinimumDisplayTime;
- if (updatePostTime) {
- mPostTime = Math.max(mPostTime, now);
- }
+ if (updatePostTime) {
+ mPostTime = Math.max(mPostTime, now);
+ }
+ };
+ mAvalancheController.update(this, runnable, "updateEntry (updatePostTime)");
if (isSticky()) {
- removeAutoRemovalCallbacks("updateEntry (sticky)");
+ cancelAutoRemovalCallbacks("updateEntry (sticky)");
return;
}
- final long finishTime = calculateFinishTime();
- final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime);
- scheduleAutoRemovalCallback(timeLeft, "updateEntry (not sticky)");
+ FinishTimeUpdater finishTimeCalculator = () -> {
+ final long finishTime = calculateFinishTime();
+ final long now = mSystemClock.elapsedRealtime();
+ final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime);
+ return timeLeft;
+ };
+ scheduleAutoRemovalCallback(finishTimeCalculator, "updateEntry (not sticky)");
}
/**
@@ -758,12 +816,31 @@
}
}
+ @Override
+ public int hashCode() {
+ if (mEntry == null) return super.hashCode();
+ int result = mEntry.getKey().hashCode();
+ result = 31 * result;
+ return result;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || !(o instanceof HeadsUpEntry)) return false;
+ HeadsUpEntry otherHeadsUpEntry = (HeadsUpEntry) o;
+ if (mEntry != null && otherHeadsUpEntry.mEntry != null) {
+ return mEntry.getKey().equals(otherHeadsUpEntry.mEntry.getKey());
+ }
+ return false;
+ }
+
public void setExpanded(boolean expanded) {
this.mExpanded = expanded;
}
public void reset() {
- removeAutoRemovalCallbacks("reset()");
+ cancelAutoRemovalCallbacks("reset()");
mEntry = null;
mRemoveRunnable = null;
mExpanded = false;
@@ -773,37 +850,48 @@
/**
* Clear any pending removal runnables.
*/
- public void removeAutoRemovalCallbacks(@Nullable String reason) {
- final boolean removed = removeAutoRemovalCallbackInternal();
+ public void cancelAutoRemovalCallbacks(@Nullable String reason) {
+ Runnable runnable = () -> {
+ final boolean removed = cancelAutoRemovalCallbackInternal();
- if (removed) {
- mLogger.logAutoRemoveCanceled(mEntry, reason);
- }
+ if (removed) {
+ mLogger.logAutoRemoveCanceled(mEntry, reason);
+ }
+ };
+ mAvalancheController.update(this, runnable,
+ reason + " removeAutoRemovalCallbacks");
}
- public void scheduleAutoRemovalCallback(long delayMillis, @NonNull String reason) {
- if (mRemoveRunnable == null) {
- Log.wtf(TAG, "scheduleAutoRemovalCallback with no callback set");
- return;
- }
+ public void scheduleAutoRemovalCallback(FinishTimeUpdater finishTimeCalculator,
+ @NonNull String reason) {
- final boolean removed = removeAutoRemovalCallbackInternal();
+ Runnable runnable = () -> {
+ long delayMs = finishTimeCalculator.updateAndGetTimeRemaining();
- if (removed) {
- mLogger.logAutoRemoveRescheduled(mEntry, delayMillis, reason);
- } else {
- mLogger.logAutoRemoveScheduled(mEntry, delayMillis, reason);
- }
+ if (mRemoveRunnable == null) {
+ Log.wtf(TAG, "scheduleAutoRemovalCallback with no callback set");
+ return;
+ }
- mCancelRemoveRunnable = mExecutor.executeDelayed(mRemoveRunnable,
- delayMillis);
+ final boolean deletedExistingRemovalRunnable = cancelAutoRemovalCallbackInternal();
+ mCancelRemoveRunnable = mExecutor.executeDelayed(mRemoveRunnable,
+ delayMs);
+
+ if (deletedExistingRemovalRunnable) {
+ mLogger.logAutoRemoveRescheduled(mEntry, delayMs, reason);
+ } else {
+ mLogger.logAutoRemoveScheduled(mEntry, delayMs, reason);
+ }
+ };
+ mAvalancheController.update(this, runnable,
+ reason + " scheduleAutoRemovalCallback");
}
- public boolean removeAutoRemovalCallbackInternal() {
+ public boolean cancelAutoRemovalCallbackInternal() {
final boolean scheduled = (mCancelRemoveRunnable != null);
if (scheduled) {
- mCancelRemoveRunnable.run();
+ mCancelRemoveRunnable.run(); // Delete removal runnable from Executor queue
mCancelRemoveRunnable = null;
}
@@ -815,8 +903,12 @@
*/
public void removeAsSoonAsPossible() {
if (mRemoveRunnable != null) {
- final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime();
- scheduleAutoRemovalCallback(timeLeft, "removeAsSoonAsPossible");
+
+ FinishTimeUpdater finishTimeCalculator = () -> {
+ final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime();
+ return timeLeft;
+ };
+ scheduleAutoRemovalCallback(finishTimeCalculator, "removeAsSoonAsPossible");
}
}
@@ -834,9 +926,15 @@
* {@link SystemClock#elapsedRealtime()}
*/
protected long calculateFinishTime() {
- final long duration = getRecommendedHeadsUpTimeoutMs(
- isStickyForSomeTime() ? mStickyForSomeTimeAutoDismissTime : mAutoDismissTime);
-
+ int requestedTimeOutMs;
+ if (isStickyForSomeTime()) {
+ requestedTimeOutMs = mStickyForSomeTimeAutoDismissTime;
+ } else if (mAvalancheController.shortenDuration(this)) {
+ requestedTimeOutMs = 1000;
+ } else {
+ requestedTimeOutMs = mAutoDismissTime;
+ }
+ final long duration = getRecommendedHeadsUpTimeoutMs(requestedTimeOutMs);
return mPostTime + duration;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 5bced93..9633cb0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -135,7 +135,6 @@
@Nullable
private RevealParams mRevealParams;
private Rect mContentBackgroundBounds;
- private boolean mIsFocusAnimationFlagActive;
private boolean mIsAnimatingAppearance = false;
// TODO(b/193539698): move these to a Controller
@@ -432,7 +431,7 @@
// case to prevent flicker.
if (!mRemoved) {
ViewGroup parent = (ViewGroup) getParent();
- if (animate && parent != null && mIsFocusAnimationFlagActive) {
+ if (animate && parent != null) {
ViewGroup grandParent = (ViewGroup) parent.getParent();
View actionsContainer = getActionsContainerLayout();
@@ -497,8 +496,7 @@
}
private void setTopMargin(int topMargin) {
- if (!(getLayoutParams() instanceof FrameLayout.LayoutParams)) return;
- final FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
+ if (!(getLayoutParams() instanceof FrameLayout.LayoutParams layoutParams)) return;
layoutParams.topMargin = topMargin;
setLayoutParams(layoutParams);
}
@@ -608,24 +606,10 @@
}
/**
- * Sets whether the feature flag for the revised inline reply animation is active or not.
- * @param active
- */
- public void setIsFocusAnimationFlagActive(boolean active) {
- mIsFocusAnimationFlagActive = active;
- }
-
- /**
* Focuses the RemoteInputView and animates its appearance
*/
public void focusAnimated() {
- if (!mIsFocusAnimationFlagActive && getVisibility() != VISIBLE
- && mRevealParams != null) {
- android.animation.Animator animator = mRevealParams.createCircularRevealAnimator(this);
- animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- animator.start();
- } else if (mIsFocusAnimationFlagActive && getVisibility() != VISIBLE) {
+ if (getVisibility() != VISIBLE) {
mIsAnimatingAppearance = true;
setAlpha(0f);
Animator focusAnimator = getFocusAnimator(getActionsContainerLayout());
@@ -680,37 +664,19 @@
}
private void reset() {
- if (mIsFocusAnimationFlagActive) {
- mProgressBar.setVisibility(INVISIBLE);
- mResetting = true;
- mSending = false;
- mController.removeSpinning(mEntry.getKey(), mToken);
- onDefocus(true /* animate */, false /* logClose */, () -> {
- mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());
- mEditText.getText().clear();
- mEditText.setEnabled(isAggregatedVisible());
- mSendButton.setVisibility(VISIBLE);
- updateSendButton();
- setAttachment(null);
- mResetting = false;
- });
- return;
- }
-
+ mProgressBar.setVisibility(INVISIBLE);
mResetting = true;
mSending = false;
- mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());
-
- mEditText.getText().clear();
- mEditText.setEnabled(isAggregatedVisible());
- mSendButton.setVisibility(VISIBLE);
- mProgressBar.setVisibility(INVISIBLE);
mController.removeSpinning(mEntry.getKey(), mToken);
- updateSendButton();
- onDefocus(false /* animate */, false /* logClose */, null /* doAfterDefocus */);
- setAttachment(null);
-
- mResetting = false;
+ onDefocus(true /* animate */, false /* logClose */, () -> {
+ mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());
+ mEditText.getText().clear();
+ mEditText.setEnabled(isAggregatedVisible());
+ mSendButton.setVisibility(VISIBLE);
+ updateSendButton();
+ setAttachment(null);
+ mResetting = false;
+ });
}
@Override
@@ -854,7 +820,7 @@
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
- if (mIsFocusAnimationFlagActive) setPivotY(getMeasuredHeight());
+ setPivotY(getMeasuredHeight());
if (mContentBackgroundBounds != null) {
mContentBackground.setBounds(mContentBackgroundBounds);
}
@@ -1015,9 +981,9 @@
private RemoteInputView mRemoteInputView;
boolean mShowImeOnInputConnection;
- private LightBarController mLightBarController;
+ private final LightBarController mLightBarController;
private InputMethodManager mInputMethodManager;
- private ArraySet<String> mSupportedMimes = new ArraySet<>();
+ private final ArraySet<String> mSupportedMimes = new ArraySet<>();
UserHandle mUser;
public RemoteEditText(Context context, AttributeSet attrs) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
index 6c0d433..bfee9ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
@@ -32,7 +32,6 @@
import com.android.internal.logging.UiEventLogger
import com.android.systemui.res.R
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags.NOTIFICATION_INLINE_REPLY_ANIMATION
import com.android.systemui.statusbar.NotificationRemoteInputManager
import com.android.systemui.statusbar.RemoteInputController
import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -64,8 +63,6 @@
var revealParams: RevealParams?
- val isFocusAnimationFlagActive: Boolean
-
/**
* Sets the smart reply that should be inserted in the remote input, or `null` if the user is
* not editing a smart reply.
@@ -155,9 +152,6 @@
override val isActive: Boolean get() = view.isActive
- override val isFocusAnimationFlagActive: Boolean
- get() = mFlags.isEnabled(NOTIFICATION_INLINE_REPLY_ANIMATION)
-
override fun bind() {
if (isBound) return
isBound = true
@@ -168,7 +162,6 @@
view.setSupportedMimeTypes(it.allowedDataTypes)
}
view.setRevealParameters(revealParams)
- view.setIsFocusAnimationFlagActive(isFocusAnimationFlagActive)
view.addOnEditTextFocusChangedListener(onFocusChangeListener)
view.addOnSendRemoteInputListener(onSendRemoteInputListener)
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
index 5c53ff9..ac1d280 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
@@ -16,6 +16,8 @@
package com.android.systemui.unfold
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.annotation.BinderThread
import android.content.Context
@@ -23,7 +25,6 @@
import android.os.SystemProperties
import android.util.Log
import android.view.animation.DecelerateInterpolator
-import androidx.core.animation.addListener
import com.android.internal.foldables.FoldLockSettingAvailabilityProvider
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DeviceStateRepository
@@ -36,17 +37,25 @@
import com.android.systemui.unfold.dagger.UnfoldBg
import com.android.systemui.util.animation.data.repository.AnimationStatusRepository
import javax.inject.Inject
+import kotlin.coroutines.resume
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.android.asCoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeout
+@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
class FoldLightRevealOverlayAnimation
@Inject
constructor(
@@ -61,6 +70,9 @@
private val revealProgressValueAnimator: ValueAnimator =
ValueAnimator.ofFloat(ALPHA_OPAQUE, ALPHA_TRANSPARENT)
+ private val areAnimationEnabled: Flow<Boolean>
+ get() = animationStatusRepository.areAnimationsEnabled()
+
private lateinit var controller: FullscreenLightRevealAnimationController
@Volatile private var readyCallback: CompletableDeferred<Runnable>? = null
@@ -89,33 +101,31 @@
applicationScope.launch(bgHandler.asCoroutineDispatcher()) {
deviceStateRepository.state
- .map { it != DeviceStateRepository.DeviceState.FOLDED }
+ .map { it == DeviceStateRepository.DeviceState.FOLDED }
.distinctUntilChanged()
- .filter { isUnfolded -> isUnfolded }
- .collect { controller.ensureOverlayRemoved() }
- }
-
- applicationScope.launch(bgHandler.asCoroutineDispatcher()) {
- deviceStateRepository.state
- .filter {
- animationStatusRepository.areAnimationsEnabled().first() &&
- it == DeviceStateRepository.DeviceState.FOLDED
- }
- .collect {
- try {
- withTimeout(WAIT_FOR_ANIMATION_TIMEOUT_MS) {
- readyCallback = CompletableDeferred()
- val onReady = readyCallback?.await()
- readyCallback = null
- controller.addOverlay(ALPHA_OPAQUE, onReady)
- waitForScreenTurnedOn()
+ .flatMapLatest { isFolded ->
+ flow<Nothing> {
+ if (!areAnimationEnabled.first() || !isFolded) {
+ return@flow
+ }
+ withTimeout(WAIT_FOR_ANIMATION_TIMEOUT_MS) {
+ readyCallback = CompletableDeferred()
+ val onReady = readyCallback?.await()
+ readyCallback = null
+ controller.addOverlay(ALPHA_OPAQUE, onReady)
+ waitForScreenTurnedOn()
+ }
playFoldLightRevealOverlayAnimation()
}
- } catch (e: TimeoutCancellationException) {
- Log.e(TAG, "Fold light reveal animation timed out")
- ensureOverlayRemovedInternal()
- }
+ .catchTimeoutAndLog()
+ .onCompletion {
+ controller.ensureOverlayRemoved()
+ val onReady = readyCallback?.takeIf { it.isCompleted }?.getCompleted()
+ onReady?.run()
+ readyCallback = null
+ }
}
+ .collect {}
}
}
@@ -128,19 +138,34 @@
powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first()
}
- private fun ensureOverlayRemovedInternal() {
- revealProgressValueAnimator.cancel()
- controller.ensureOverlayRemoved()
- }
-
- private fun playFoldLightRevealOverlayAnimation() {
+ private suspend fun playFoldLightRevealOverlayAnimation() {
revealProgressValueAnimator.duration = ANIMATION_DURATION
revealProgressValueAnimator.interpolator = DecelerateInterpolator()
revealProgressValueAnimator.addUpdateListener { animation ->
controller.updateRevealAmount(animation.animatedFraction)
}
- revealProgressValueAnimator.addListener(onEnd = { controller.ensureOverlayRemoved() })
- revealProgressValueAnimator.start()
+ revealProgressValueAnimator.startAndAwaitCompletion()
+ }
+
+ private suspend fun ValueAnimator.startAndAwaitCompletion(): Unit =
+ suspendCancellableCoroutine { continuation ->
+ val listener =
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ continuation.resume(Unit)
+ removeListener(this)
+ }
+ }
+ addListener(listener)
+ continuation.invokeOnCancellation { removeListener(listener) }
+ start()
+ }
+
+ private fun <T> Flow<T>.catchTimeoutAndLog() = catch { exception ->
+ when (exception) {
+ is TimeoutCancellationException -> Log.e(TAG, "Fold light reveal animation timed out")
+ else -> throw exception
+ }
}
private companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
index cabe831..d10554f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
@@ -32,7 +32,7 @@
private const val LIMIT_BACKGROUND_DISPATCHER_THREADS = true
-/** Providers for various SystemIU specific coroutines-related constructs. */
+/** Providers for various SystemUI-specific coroutines-related constructs. */
@Module
class SysUICoroutinesModule {
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
index 71df8e5..1bceee9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
@@ -36,3 +36,7 @@
/** Current media state is unknown yet. */
data object Unknown : MediaDeviceSession
}
+
+/** Returns true when the audio is playing for the [MediaDeviceSession]. */
+fun MediaDeviceSession.isPlaying(): Boolean =
+ this is MediaDeviceSession.Active && playbackState?.isActive == true
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index 37661b5..d49cb1e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -24,6 +24,7 @@
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.isPlaying
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
import javax.inject.Inject
@@ -110,9 +111,6 @@
null,
)
- private fun MediaDeviceSession.isPlaying(): Boolean =
- this is MediaDeviceSession.Active && playbackState?.isActive == true
-
fun onBarClick(expandable: Expandable) {
actionsInteractor.onBarClick(mediaDeviceSession.value, expandable)
volumePanelViewModel.dismissPanel()
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index faf7434..532e517 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -21,6 +21,7 @@
import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.settingslib.volume.shared.model.RingerMode
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.res.R
import com.android.systemui.volume.panel.component.volume.domain.interactor.VolumeSliderInteractor
@@ -54,14 +55,6 @@
AudioStream(AudioManager.STREAM_NOTIFICATION) to R.drawable.ic_volume_ringer,
AudioStream(AudioManager.STREAM_ALARM) to R.drawable.ic_volume_alarm,
)
- private val mutedIconsByStream =
- mapOf(
- AudioStream(AudioManager.STREAM_MUSIC) to R.drawable.ic_volume_off,
- AudioStream(AudioManager.STREAM_VOICE_CALL) to R.drawable.ic_volume_off,
- AudioStream(AudioManager.STREAM_RING) to R.drawable.ic_volume_off,
- AudioStream(AudioManager.STREAM_NOTIFICATION) to R.drawable.ic_volume_off,
- AudioStream(AudioManager.STREAM_ALARM) to R.drawable.ic_volume_off,
- )
private val labelsByStream =
mapOf(
AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_music,
@@ -74,6 +67,8 @@
mapOf(
AudioStream(AudioManager.STREAM_NOTIFICATION) to
R.string.stream_notification_unavailable,
+ AudioStream(AudioManager.STREAM_ALARM) to R.string.stream_alarm_unavailable,
+ AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_media_unavailable,
)
private var value = 0f
@@ -81,8 +76,9 @@
combine(
audioVolumeInteractor.getAudioStream(audioStream),
audioVolumeInteractor.canChangeVolume(audioStream),
- ) { model, isEnabled ->
- model.toState(value, isEnabled)
+ audioVolumeInteractor.ringerMode,
+ ) { model, isEnabled, ringerMode ->
+ model.toState(value, isEnabled, ringerMode)
}
.stateIn(coroutineScope, SharingStarted.Eagerly, EmptyState)
@@ -100,7 +96,11 @@
}
}
- private fun AudioStreamModel.toState(value: Float, isEnabled: Boolean): State {
+ private fun AudioStreamModel.toState(
+ value: Float,
+ isEnabled: Boolean,
+ ringerMode: RingerMode,
+ ): State {
return State(
value =
volumeSliderInteractor.processVolumeToValue(
@@ -110,7 +110,7 @@
isMuted,
),
valueRange = volumeSliderInteractor.displayValueRange,
- icon = getIcon(this),
+ icon = getIcon(ringerMode),
label = labelsByStream[audioStream]?.let(context::getString)
?: error("No label for the stream: $audioStream"),
disabledMessage = disabledTextByStream[audioStream]?.let(context::getString),
@@ -119,14 +119,31 @@
)
}
- private fun getIcon(model: AudioStreamModel): Icon {
- val isMutedOrNoVolume = model.isMuted || model.volume == model.minVolume
+ private fun AudioStreamModel.getIcon(ringerMode: RingerMode): Icon {
+ val isMutedOrNoVolume = isMuted || volume == minVolume
val iconRes =
if (isMutedOrNoVolume) {
- mutedIconsByStream
+ when (audioStream.value) {
+ AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_off
+ AudioManager.STREAM_VOICE_CALL -> R.drawable.ic_volume_off
+ AudioManager.STREAM_RING ->
+ if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
+ R.drawable.ic_volume_ringer_vibrate
+ } else {
+ R.drawable.ic_volume_off
+ }
+ AudioManager.STREAM_NOTIFICATION ->
+ if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
+ R.drawable.ic_volume_ringer_vibrate
+ } else {
+ R.drawable.ic_volume_off
+ }
+ AudioManager.STREAM_ALARM -> R.drawable.ic_volume_off
+ else -> null
+ }
} else {
- iconsByStream
- }[audioStream]
+ iconsByStream[audioStream]
+ }
?: error("No icon for the stream: $audioStream")
return Icon.Resource(iconRes, null)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
index 2824323..aaee24b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
@@ -18,6 +18,8 @@
import android.media.AudioManager
import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.isPlaying
import com.android.systemui.volume.panel.component.volume.domain.interactor.CastVolumeInteractor
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.AudioStreamSliderViewModel
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.CastVolumeSliderViewModel
@@ -28,12 +30,17 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
+import kotlinx.coroutines.launch
/**
* Controls the behaviour of the whole audio
@@ -46,6 +53,7 @@
constructor(
@VolumePanelScope private val scope: CoroutineScope,
castVolumeInteractor: CastVolumeInteractor,
+ mediaOutputInteractor: MediaOutputInteractor,
private val streamSliderViewModelFactory: AudioStreamSliderViewModel.Factory,
private val castVolumeSliderViewModelFactory: CastVolumeSliderViewModel.Factory,
) {
@@ -90,4 +98,17 @@
remoteSessionsViewModels + streamViewModels
}
.stateIn(scope, SharingStarted.Eagerly, emptyList())
+
+ private val mutableIsExpanded = MutableSharedFlow<Boolean>()
+
+ val isExpanded: StateFlow<Boolean> =
+ merge(
+ mutableIsExpanded.onStart { emit(false) },
+ mediaOutputInteractor.mediaDeviceSession.map { !it.isPlaying() },
+ )
+ .stateIn(scope, SharingStarted.Eagerly, false)
+
+ fun onExpandedChanged(isExpanded: Boolean) {
+ scope.launch { mutableIsExpanded.emit(isExpanded) }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
index dd428f5..ccdcee5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
@@ -32,7 +32,6 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -57,7 +56,6 @@
@Before
public void setUp() throws Exception {
- mFeatureFlags.setDefault(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION);
mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
mDependency.injectMockDependency(NotificationMediaManager.class);
allowTestableLooperAsMainThread();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index de795a7..f76957f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -308,20 +308,6 @@
}
@Test
- @DisableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
- public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme_old() {
- mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100));
- final PointF beforePosition = mMenuView.getMenuPosition();
-
- dispatchShowingImeInsets();
-
- final float menuBottom = mMenuView.getTranslationY() + mMenuView.getMenuHeight();
- assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x);
- assertThat(menuBottom).isLessThan(beforePosition.y);
- }
-
- @Test
- @EnableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme() {
mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100));
final PointF beforePosition = mMenuView.getMenuPosition();
@@ -337,19 +323,6 @@
}
@Test
- @DisableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
- public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition_old() {
- mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 200));
- final PointF beforePosition = mMenuView.getMenuPosition();
-
- dispatchHidingImeInsets();
-
- assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x);
- assertThat(mMenuView.getTranslationY()).isEqualTo(beforePosition.y);
- }
-
- @Test
- @EnableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition() {
mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 200));
final PointF beforePosition = mMenuView.getMenuPosition();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 10b86ea..2b4e9ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -20,7 +20,6 @@
import android.hardware.biometrics.BiometricAuthenticator
import android.hardware.biometrics.BiometricConstants
import android.hardware.biometrics.BiometricManager
-import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT
import android.hardware.biometrics.PromptInfo
import android.hardware.biometrics.PromptVerticalListContentView
import android.hardware.face.FaceSensorPropertiesInternal
@@ -386,7 +385,6 @@
@Test
fun testShowCredentialUI_withDescription() {
- mSetFlagsRule.disableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
val container = initializeFingerprintContainer(
authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
)
@@ -397,6 +395,7 @@
}
@Test
+ @Ignore("b/302735104")
fun testShowCredentialUI_withCustomBp() {
val container = initializeFingerprintContainer(
authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
index 7b972d3..81d4e83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
@@ -17,9 +17,11 @@
package com.android.systemui.biometrics.data.repository
import android.hardware.biometrics.BiometricManager
+import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT
import android.hardware.biometrics.PromptInfo
import android.hardware.biometrics.PromptVerticalListContentView
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.shared.model.PromptKind
@@ -135,6 +137,8 @@
@Test
fun showBpWithoutIconForCredential_withCustomBp() =
testScope.runTest {
+ mSetFlagsRule.enableFlags(Flags.FLAG_CONSTRAINT_BP)
+ mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
for (case in
listOf(
PromptKind.Biometric(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 140849b..7db4ca9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -35,6 +35,7 @@
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.Flags.FLAG_BP_TALKBACK
+import com.android.systemui.Flags.FLAG_CONSTRAINT_BP
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.UdfpsUtils
@@ -1256,6 +1257,7 @@
fun descriptionOverriddenByContentView() =
runGenericTest(contentView = promptContentView, description = "test description") {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val contentView by collectLastValue(viewModel.contentView)
val description by collectLastValue(viewModel.description)
@@ -1267,6 +1269,7 @@
fun descriptionWithoutContentView() =
runGenericTest(description = "test description") {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val contentView by collectLastValue(viewModel.contentView)
val description by collectLastValue(viewModel.description)
@@ -1278,6 +1281,7 @@
fun logoIsNullIfPackageNameNotFound() =
runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logo by collectLastValue(viewModel.logo)
assertThat(logo).isNull()
}
@@ -1285,6 +1289,7 @@
@Test
fun defaultLogoIfNoLogoSet() = runGenericTest {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logo by collectLastValue(viewModel.logo)
assertThat(logo).isEqualTo(defaultLogoIcon)
}
@@ -1293,6 +1298,7 @@
fun logoResSetByApp() =
runGenericTest(logoRes = logoResFromApp) {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logo by collectLastValue(viewModel.logo)
assertThat(logo).isEqualTo(logoFromApp)
}
@@ -1301,6 +1307,7 @@
fun logoBitmapSetByApp() =
runGenericTest(logoBitmap = logoBitmapFromApp) {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logo by collectLastValue(viewModel.logo)
assertThat((logo as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp)
}
@@ -1309,6 +1316,7 @@
fun logoDescriptionIsEmptyIfPackageNameNotFound() =
runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logoDescription by collectLastValue(viewModel.logoDescription)
assertThat(logoDescription).isEqualTo("")
}
@@ -1316,6 +1324,7 @@
@Test
fun defaultLogoDescriptionIfNoLogoDescriptionSet() = runGenericTest {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logoDescription by collectLastValue(viewModel.logoDescription)
assertThat(logoDescription).isEqualTo(defaultLogoDescription)
}
@@ -1324,10 +1333,22 @@
fun logoDescriptionSetByApp() =
runGenericTest(logoDescription = logoDescriptionFromApp) {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logoDescription by collectLastValue(viewModel.logoDescription)
assertThat(logoDescription).isEqualTo(logoDescriptionFromApp)
}
+ @Test
+ fun iconViewLoaded() = runGenericTest {
+ val isIconViewLoaded by collectLastValue(viewModel.isIconViewLoaded)
+ // TODO(b/328677869): Add test for noIcon logic.
+ assertThat(isIconViewLoaded).isFalse()
+
+ viewModel.setIsIconViewLoaded(true)
+
+ assertThat(isIconViewLoaded).isTrue()
+ }
+
/** Asserts that the selected buttons are visible now. */
private suspend fun TestScope.assertButtonsVisible(
tryAgain: Boolean = false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
index e796303..701b703 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
@@ -401,7 +401,7 @@
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
Pair("Enter PIN", "PIN is required after lockdown"),
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
- Pair("Enter PIN", "Update will install when device not in use"),
+ Pair("Enter PIN", "PIN required for additional security"),
LockPatternUtils.StrongAuthTracker
.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
Pair(
@@ -439,7 +439,7 @@
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
Pair("Enter PIN", "PIN is required after lockdown"),
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
- Pair("Enter PIN", "Update will install when device not in use"),
+ Pair("Enter PIN", "PIN required for additional security"),
LockPatternUtils.StrongAuthTracker
.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
Pair(
@@ -481,7 +481,7 @@
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
Pair("Enter PIN", "PIN is required after lockdown"),
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
- Pair("Enter PIN", "Update will install when device not in use"),
+ Pair("Enter PIN", "PIN required for additional security"),
LockPatternUtils.StrongAuthTracker
.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
Pair(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index 3f13033..45d20dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -188,6 +188,20 @@
}
@Test
+ public void testRegisterSensor_OccludingActivity() {
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
+
+ ArgumentCaptor<StatusBarStateController.StateListener> stateListenerArgumentCaptor =
+ ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
+ verify(mStatusBarStateController).addCallback(stateListenerArgumentCaptor.capture());
+
+ mFalsingCollector.onScreenTurningOn();
+ reset(mProximitySensor);
+ stateListenerArgumentCaptor.getValue().onStateChanged(StatusBarState.SHADE);
+ verify(mProximitySensor).register(any(ThresholdSensor.Listener.class));
+ }
+
+ @Test
public void testPassThroughGesture() {
MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index e1e9fcb..dac88a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
@@ -532,6 +532,18 @@
assertThat(faceAuthRepository.runningAuthRequest.value).isNull()
}
+ @Test
+ fun lockedOut_providesSameValueFromRepository() =
+ testScope.runTest {
+ assertThat(underTest.lockedOut).isSameInstanceAs(faceAuthRepository.isLockedOut)
+ }
+
+ @Test
+ fun authenticated_providesSameValueFromRepository() =
+ testScope.runTest {
+ assertThat(underTest.authenticated).isSameInstanceAs(faceAuthRepository.isAuthenticated)
+ }
+
companion object {
private const val primaryUserId = 1
private val primaryUser = UserInfo(primaryUserId, "test user", UserInfo.FLAG_PRIMARY)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorTest.kt
new file mode 100644
index 0000000..decbdaf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorTest.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.deviceentry.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceEntryFingerprintAuthInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.deviceEntryFingerprintAuthInteractor
+ private val fingerprintAuthRepository = kosmos.deviceEntryFingerprintAuthRepository
+ private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
+ private val biometricSettingsRepository = kosmos.biometricSettingsRepository
+
+ @Test
+ fun isFingerprintAuthCurrentlyAllowed_allowedOnlyWhenItIsNotLockedOutAndAllowedBySettings() =
+ testScope.runTest {
+ val currentlyAllowed by collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)
+ biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
+ fingerprintAuthRepository.setLockedOut(true)
+
+ assertThat(currentlyAllowed).isFalse()
+
+ fingerprintAuthRepository.setLockedOut(false)
+ assertThat(currentlyAllowed).isTrue()
+
+ biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(false)
+ assertThat(currentlyAllowed).isFalse()
+ }
+
+ @Test
+ fun isSensorUnderDisplay_trueForUdfpsSensorTypes() =
+ testScope.runTest {
+ val isSensorUnderDisplay by collectLastValue(underTest.isSensorUnderDisplay)
+
+ fingerprintPropertyRepository.supportsUdfps()
+ assertThat(isSensorUnderDisplay).isTrue()
+
+ fingerprintPropertyRepository.supportsRearFps()
+ assertThat(isSensorUnderDisplay).isFalse()
+
+ fingerprintPropertyRepository.supportsSideFps()
+ assertThat(isSensorUnderDisplay).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 0bd4cbe..1849245 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -93,11 +93,11 @@
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.ui.viewmodel.DreamViewModel;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.SystemPropertiesHelper;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -220,7 +220,7 @@
private boolean mKeyguardGoingAway = false;
private @Mock CoroutineDispatcher mDispatcher;
- private @Mock DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel;
+ private @Mock DreamViewModel mDreamViewModel;
private @Mock SystemPropertiesHelper mSystemPropertiesHelper;
private @Mock SceneContainerFlags mSceneContainerFlags;
@@ -241,9 +241,9 @@
final ViewRootImpl testViewRoot = mock(ViewRootImpl.class);
when(testViewRoot.getView()).thenReturn(mock(View.class));
when(mStatusBarKeyguardViewManager.getViewRootImpl()).thenReturn(testViewRoot);
- when(mDreamingToLockscreenTransitionViewModel.getDreamOverlayAlpha())
+ when(mDreamViewModel.getDreamAlpha())
.thenReturn(mock(Flow.class));
- when(mDreamingToLockscreenTransitionViewModel.getTransitionEnded())
+ when(mDreamViewModel.getTransitionEnded())
.thenReturn(mock(Flow.class));
when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(mDefaultUserId);
when(mSelectedUserInteractor.getSelectedUserId(anyBoolean())).thenReturn(mDefaultUserId);
@@ -1259,7 +1259,7 @@
mSystemSettings,
mSystemClock,
mDispatcher,
- () -> mDreamingToLockscreenTransitionViewModel,
+ () -> mDreamViewModel,
mSystemPropertiesHelper,
() -> mock(WindowManagerLockscreenVisibilityManager.class),
mSelectedUserInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
index 22a2e93..f252163 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
@@ -130,6 +130,24 @@
assertThat(value()).isEqualTo(LARGE)
}
+ @Test
+ fun isLargeClockVisible_whenLargeClockSize_isTrue() =
+ scope.runTest {
+ fakeSettings.putInt(LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1)
+ keyguardClockRepository.setClockSize(LARGE)
+ var value = collectLastValue(underTest.isLargeClockVisible)
+ assertThat(value()).isEqualTo(true)
+ }
+
+ @Test
+ fun isLargeClockVisible_whenSmallClockSize_isFalse() =
+ scope.runTest {
+ fakeSettings.putInt(LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1)
+ keyguardClockRepository.setClockSize(SMALL)
+ var value = collectLastValue(underTest.isLargeClockVisible)
+ assertThat(value()).isEqualTo(false)
+ }
+
private fun setupMockClock() {
whenever(clock.largeClock).thenReturn(largeClock)
whenever(largeClock.config).thenReturn(clockFaceConfig)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index bcec6109..b80dcd4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -62,6 +62,7 @@
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth
import kotlin.math.min
+import kotlin.test.assertEquals
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
@@ -77,7 +78,6 @@
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
-import kotlin.test.assertEquals
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -134,7 +134,12 @@
private lateinit var lockscreenToPrimaryBouncerTransitionViewModel:
LockscreenToPrimaryBouncerTransitionViewModel
@Mock
- private lateinit var transitionInteractor: KeyguardTransitionInteractor
+ private lateinit var lockscreenToGlanceableHubTransitionViewModel:
+ LockscreenToGlanceableHubTransitionViewModel
+ @Mock
+ private lateinit var glanceableHubToLockscreenTransitionViewModel:
+ GlanceableHubToLockscreenTransitionViewModel
+ @Mock private lateinit var transitionInteractor: KeyguardTransitionInteractor
private lateinit var underTest: KeyguardQuickAffordancesCombinedViewModel
@@ -271,6 +276,10 @@
whenever(lockscreenToOccludedTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow())
whenever(lockscreenToPrimaryBouncerTransitionViewModel.shortcutsAlpha)
.thenReturn(emptyFlow())
+ whenever(lockscreenToGlanceableHubTransitionViewModel.shortcutsAlpha)
+ .thenReturn(emptyFlow())
+ whenever(glanceableHubToLockscreenTransitionViewModel.shortcutsAlpha)
+ .thenReturn(emptyFlow())
whenever(shadeInteractor.anyExpansion).thenReturn(intendedShadeAlphaMutableStateFlow)
whenever(transitionInteractor.finishedKeyguardState)
.thenReturn(intendedFinishedKeyguardStateFlow)
@@ -307,6 +316,8 @@
offToLockscreenTransitionViewModel = offToLockscreenTransitionViewModel,
primaryBouncerToLockscreenTransitionViewModel =
primaryBouncerToLockscreenTransitionViewModel,
+ glanceableHubToLockscreenTransitionViewModel =
+ glanceableHubToLockscreenTransitionViewModel,
lockscreenToAodTransitionViewModel = lockscreenToAodTransitionViewModel,
lockscreenToDozingTransitionViewModel = lockscreenToDozingTransitionViewModel,
lockscreenToDreamingHostedTransitionViewModel =
@@ -316,6 +327,8 @@
lockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel,
lockscreenToPrimaryBouncerTransitionViewModel =
lockscreenToPrimaryBouncerTransitionViewModel,
+ lockscreenToGlanceableHubTransitionViewModel =
+ lockscreenToGlanceableHubTransitionViewModel,
transitionInteractor = transitionInteractor,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 31746a2..b38d5e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -99,6 +99,7 @@
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.shared.rotation.RotationButtonController;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.CommandQueue;
@@ -580,6 +581,7 @@
() -> Optional.of(mCentralSurfaces),
mKeyguardStateController,
mock(ShadeViewController.class),
+ mock(PanelExpansionInteractor.class),
mock(NotificationRemoteInputManager.class),
mock(NotificationShadeDepthController.class),
mHandler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index 65ede89..0101741 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -51,6 +51,7 @@
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import com.android.systemui.util.animation.DisappearParameters;
@@ -100,6 +101,8 @@
Configuration mConfiguration;
@Mock
Runnable mHorizontalLayoutListener;
+ @Mock
+ VibratorHelper mVibratorHelper;
private QSPanelControllerBase<QSPanel> mController;
@@ -110,7 +113,8 @@
MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
DumpManager dumpManager) {
super(view, host, qsCustomizerController, true, mediaHost, metricsLogger, uiEventLogger,
- qsLogger, dumpManager, new ResourcesSplitShadeStateController());
+ qsLogger, dumpManager, new ResourcesSplitShadeStateController(),
+ mVibratorHelper);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index 85d7d98..916e8dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -19,6 +19,7 @@
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
import com.android.systemui.settings.brightness.BrightnessController
import com.android.systemui.settings.brightness.BrightnessSliderController
+import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
import com.android.systemui.tuner.TunerService
@@ -61,6 +62,7 @@
@Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
@Mock private lateinit var configuration: Configuration
@Mock private lateinit var pagedTileLayout: PagedTileLayout
+ @Mock private lateinit var vibratorHelper: VibratorHelper
private val sceneContainerFlags = FakeSceneContainerFlags()
@@ -101,6 +103,7 @@
statusBarKeyguardViewManager,
ResourcesSplitShadeStateController(),
sceneContainerFlags,
+ vibratorHelper,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index 2c14308..71a9a8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.customize.QSCustomizerController
import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
import com.android.systemui.util.leak.RotationUtils
import org.junit.After
@@ -59,6 +60,7 @@
@Mock private lateinit var tile: QSTile
@Mock private lateinit var tileLayout: TileLayout
@Captor private lateinit var captor: ArgumentCaptor<QSPanel.OnConfigurationChangedListener>
+ @Mock private lateinit var vibratorHelper: VibratorHelper
private val uiEventLogger = UiEventLoggerFake()
private val dumpManager = DumpManager()
@@ -89,7 +91,8 @@
metricsLogger,
uiEventLogger,
qsLogger,
- dumpManager
+ dumpManager,
+ vibratorHelper,
)
controller.init()
@@ -157,7 +160,8 @@
metricsLogger: MetricsLogger,
uiEventLogger: UiEventLoggerFake,
qsLogger: QSLogger,
- dumpManager: DumpManager
+ dumpManager: DumpManager,
+ vibratorHelper: VibratorHelper,
) :
QuickQSPanelController(
view,
@@ -170,7 +174,8 @@
uiEventLogger,
qsLogger,
dumpManager,
- ResourcesSplitShadeStateController()
+ ResourcesSplitShadeStateController(),
+ vibratorHelper,
) {
private var rotation = RotationUtils.ROTATION_NONE
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
index 3dc9037..0baee5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -274,8 +274,8 @@
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
screenshotExecutor.onCloseSystemDialogsReceived()
- verify(controller0).dismissScreenshot(any())
- verify(controller1).dismissScreenshot(any())
+ verify(controller0).requestDismissal(any())
+ verify(controller1).requestDismissal(any())
screenshotExecutor.onDestroy()
}
@@ -290,8 +290,8 @@
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
screenshotExecutor.onCloseSystemDialogsReceived()
- verify(controller0, never()).dismissScreenshot(any())
- verify(controller1).dismissScreenshot(any())
+ verify(controller0, never()).requestDismissal(any())
+ verify(controller1).requestDismissal(any())
screenshotExecutor.onDestroy()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
index ab90b9b..25ba09a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.haptics.slider.SeekableSliderHapticPlugin
+import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.BrightnessMirrorController
import com.android.systemui.util.mockito.any
@@ -66,6 +67,8 @@
private lateinit var listener: ToggleSlider.Listener
@Mock
private lateinit var vibratorHelper: VibratorHelper
+ @Mock
+ private lateinit var activityStarter: ActivityStarter
@Captor
private lateinit var seekBarChangeCaptor: ArgumentCaptor<SeekBar.OnSeekBarChangeListener>
@@ -91,6 +94,7 @@
mFalsingManager,
uiEventLogger,
SeekableSliderHapticPlugin(vibratorHelper, systemClock),
+ activityStarter,
)
mController.init()
mController.setOnChangedListener(listener)
@@ -120,7 +124,7 @@
@Test
fun testEnforceAdminRelayed() {
mController.setEnforcedAdmin(enforcedAdmin)
- verify(brightnessSliderView).setEnforcedAdmin(enforcedAdmin)
+ verify(brightnessSliderView).setAdminBlocker(notNull())
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 62d2d0e..07d9350 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -42,6 +42,7 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
@@ -76,6 +77,7 @@
@Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock private lateinit var shadeInteractor: ShadeInteractor
@Mock private lateinit var powerManager: PowerManager
+ @Mock private lateinit var dialogFactory: SystemUIDialogFactory
private lateinit var parentView: FrameLayout
private lateinit var containerView: View
@@ -99,6 +101,7 @@
GlanceableHubContainerController(
communalInteractor,
communalViewModel,
+ dialogFactory,
keyguardTransitionInteractor,
shadeInteractor,
powerManager
@@ -138,6 +141,7 @@
GlanceableHubContainerController(
communalInteractor,
communalViewModel,
+ dialogFactory,
keyguardTransitionInteractor,
shadeInteractor,
powerManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 617b25d..88b239a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -481,8 +481,7 @@
// AND status bar doesn't want it
whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT))
.thenReturn(false)
- // AND shade is not fully expanded
- whenever(notificationPanelViewController.isFullyExpanded()).thenReturn(false)
+ // AND shade is not fully expanded (mock is false by default)
// AND the lock icon does NOT want the touch
whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(false)
// AND quick settings controller DOES want it
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
index b548117..e90a3ac8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
@@ -157,52 +158,52 @@
}
@Test
- @DisableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_MEDIA_ICONS)
+ @DisableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS)
public void inflateMediaNotificationIconsMediaEnabled_old() throws InflationException {
finishSetupWithMediaFeatureFlagEnabled(true);
mListener.onEntryInit(mMediaEntry);
mListener.onEntryAdded(mMediaEntry);
verify(mIconManager, never()).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
mFilter.shouldFilterOut(mMediaEntry, 0);
verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
mFilter.shouldFilterOut(mMediaEntry, 0);
verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
- verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry), /* usingCache = */ eq(false));
mListener.onEntryRemoved(mMediaEntry, NotificationListenerService.REASON_CANCEL);
mListener.onEntryCleanUp(mMediaEntry);
mListener.onEntryInit(mMediaEntry);
verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
- verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry), /* usingCache = */ eq(false));
mFilter.shouldFilterOut(mMediaEntry, 0);
verify(mIconManager, times(2)).createIcons(eq(mMediaEntry));
- verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry), /* usingCache = */ eq(false));
}
@Test
- @EnableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_MEDIA_ICONS)
+ @EnableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS)
public void inflateMediaNotificationIconsMediaEnabled_new() throws InflationException {
finishSetupWithMediaFeatureFlagEnabled(true);
mListener.onEntryInit(mMediaEntry);
mListener.onEntryAdded(mMediaEntry);
verify(mIconManager).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
clearInvocations(mIconManager);
mFilter.shouldFilterOut(mMediaEntry, 0);
verify(mIconManager, never()).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
mListener.onEntryUpdated(mMediaEntry);
verify(mIconManager, never()).createIcons(eq(mMediaEntry));
- verify(mIconManager).updateIcons(eq(mMediaEntry));
+ verify(mIconManager).updateIcons(eq(mMediaEntry), /* usingCache = */ eq(false));
mListener.onEntryRemoved(mMediaEntry, NotificationListenerService.REASON_CANCEL);
mListener.onEntryCleanUp(mMediaEntry);
@@ -211,40 +212,40 @@
mListener.onEntryInit(mMediaEntry);
mListener.onEntryAdded(mMediaEntry);
verify(mIconManager).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
}
@Test
- @DisableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_MEDIA_ICONS)
+ @DisableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS)
public void inflationException_old() throws InflationException {
finishSetupWithMediaFeatureFlagEnabled(true);
mListener.onEntryInit(mMediaEntry);
mListener.onEntryAdded(mMediaEntry);
verify(mIconManager, never()).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
doThrow(InflationException.class).when(mIconManager).createIcons(eq(mMediaEntry));
mFilter.shouldFilterOut(mMediaEntry, 0);
verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
mFilter.shouldFilterOut(mMediaEntry, 0);
verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), /* usingCache = */ eq(false));
mListener.onEntryUpdated(mMediaEntry);
verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
doNothing().when(mIconManager).createIcons(eq(mMediaEntry));
mFilter.shouldFilterOut(mMediaEntry, 0);
verify(mIconManager, times(2)).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
}
@Test
- @EnableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_MEDIA_ICONS)
+ @EnableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS)
public void inflationException_new() throws InflationException {
finishSetupWithMediaFeatureFlagEnabled(true);
@@ -253,19 +254,19 @@
mListener.onEntryInit(mMediaEntry);
mListener.onEntryAdded(mMediaEntry);
verify(mIconManager).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
clearInvocations(mIconManager);
mListener.onEntryUpdated(mMediaEntry);
verify(mIconManager).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
clearInvocations(mIconManager);
doNothing().when(mIconManager).createIcons(eq(mMediaEntry));
mListener.onEntryUpdated(mMediaEntry);
verify(mIconManager).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
}
private void finishSetupWithMediaFeatureFlagEnabled(boolean mediaFeatureFlagEnabled) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
index a12806b..4ac9dc2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.statusbar.notification.icon
import android.app.ActivityManager
@@ -38,6 +40,10 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -69,6 +75,11 @@
@Mock private lateinit var notifCollection: CommonNotifCollection
@Mock private lateinit var launcherApps: LauncherApps
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+ private val mainContext = testScope.coroutineContext
+ private val bgContext = testScope.backgroundScope.coroutineContext
+
private val iconBuilder = IconBuilder(context)
private lateinit var iconManager: IconManager
@@ -85,7 +96,15 @@
`when`(shortcut.icon).thenReturn(shortcutIc)
`when`(launcherApps.getShortcutIcon(shortcut)).thenReturn(shortcutIc)
- iconManager = IconManager(notifCollection, launcherApps, iconBuilder)
+ iconManager =
+ IconManager(
+ notifCollection,
+ launcherApps,
+ iconBuilder,
+ testScope,
+ bgContext,
+ mainContext,
+ )
}
@Test
@@ -94,6 +113,7 @@
notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = true)
entry?.channel?.isImportantConversation = true
entry?.let { iconManager.createIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(shortcutIc)
}
@@ -103,6 +123,7 @@
notificationEntry(hasShortcut = false, hasMessageSenderIcon = true, hasLargeIcon = true)
entry?.channel?.isImportantConversation = true
entry?.let { iconManager.createIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(messageIc)
}
@@ -116,6 +137,7 @@
)
entry?.channel?.isImportantConversation = true
entry?.let { iconManager.createIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(largeIc)
}
@@ -129,6 +151,7 @@
)
entry?.channel?.isImportantConversation = true
entry?.let { iconManager.createIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(smallIc)
}
@@ -143,6 +166,7 @@
)
entry?.channel?.isImportantConversation = true
entry?.let { iconManager.createIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(smallIc)
}
@@ -161,6 +185,7 @@
entry?.setSensitive(true, true)
entry?.channel?.isImportantConversation = true
entry?.let { iconManager.createIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(shortcutIc)
assertThat(entry?.icons?.shelfIcon?.sourceIcon).isEqualTo(smallIc)
assertThat(entry?.icons?.aodIcon?.sourceIcon).isEqualTo(smallIc)
@@ -175,6 +200,7 @@
entry?.let { iconManager.createIcons(it) }
// Updating the icons after creation shouldn't break anything
entry?.let { iconManager.updateIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(shortcutIc)
assertThat(entry?.icons?.shelfIcon?.sourceIcon).isEqualTo(smallIc)
assertThat(entry?.icons?.aodIcon?.sourceIcon).isEqualTo(smallIc)
@@ -187,9 +213,11 @@
entry?.channel?.isImportantConversation = true
entry?.setSensitive(true, true)
entry?.let { iconManager.createIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.aodIcon?.sourceIcon).isEqualTo(smallIc)
entry?.setSensitive(false, false)
entry?.let { iconManager.updateIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.shelfIcon?.sourceIcon).isEqualTo(shortcutIc)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 42a6924..b114e13 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -60,7 +60,6 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestableContext;
import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
@@ -104,7 +103,6 @@
TestableLooper.get(this),
mFeatureFlags);
mNotificationTestHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL);
- mFeatureFlags.setDefault(Flags.SENSITIVE_REVEAL_ANIM);
}
@Test
@@ -186,14 +184,6 @@
}
@Test
- public void testSetSensitiveOnNotifRowNotifiesOfHeightChange_withOtherFlagValue()
- throws Exception {
- FakeFeatureFlags flags = mFeatureFlags;
- flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM));
- testSetSensitiveOnNotifRowNotifiesOfHeightChange();
- }
-
- @Test
public void testSetSensitiveOnNotifRowNotifiesOfHeightChange() throws Exception {
// GIVEN a sensitive notification row that's currently redacted
ExpandableNotificationRow row = mNotificationTestHelper.createRow();
@@ -210,19 +200,10 @@
// WHEN the row is set to no longer be sensitive
row.setSensitive(false, true);
- boolean expectAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
// VERIFY that the height change listener is invoked
assertThat(row.getShowingLayout()).isSameInstanceAs(row.getPrivateLayout());
assertThat(row.getIntrinsicHeight()).isGreaterThan(0);
- verify(listener).onHeightChanged(eq(row), eq(expectAnimation));
- }
-
- @Test
- public void testSetSensitiveOnGroupRowNotifiesOfHeightChange_withOtherFlagValue()
- throws Exception {
- FakeFeatureFlags flags = mFeatureFlags;
- flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM));
- testSetSensitiveOnGroupRowNotifiesOfHeightChange();
+ verify(listener).onHeightChanged(eq(row), eq(true));
}
@Test
@@ -242,19 +223,10 @@
// WHEN the row is set to no longer be sensitive
group.setSensitive(false, true);
- boolean expectAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
// VERIFY that the height change listener is invoked
assertThat(group.getShowingLayout()).isSameInstanceAs(group.getPrivateLayout());
assertThat(group.getIntrinsicHeight()).isGreaterThan(0);
- verify(listener).onHeightChanged(eq(group), eq(expectAnimation));
- }
-
- @Test
- public void testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange_withOtherFlagValue()
- throws Exception {
- FakeFeatureFlags flags = mFeatureFlags;
- flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM));
- testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange();
+ verify(listener).onHeightChanged(eq(group), eq(true));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 718f998..3b78b7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -53,6 +53,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.keyguard.TestScopeProvider;
import com.android.systemui.TestableDependency;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.flags.FakeFeatureFlags;
@@ -102,6 +103,9 @@
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
+import kotlin.coroutines.CoroutineContext;
+import kotlinx.coroutines.test.TestScope;
+
/**
* A helper class to create {@link ExpandableNotificationRow} (for both individual and group
* notifications).
@@ -140,6 +144,10 @@
private final FakeFeatureFlags mFeatureFlags;
private final SystemClock mSystemClock;
private final RowInflaterTaskLogger mRowInflaterTaskLogger;
+ private final TestScope mTestScope = TestScopeProvider.getTestScope();
+ private final CoroutineContext mBgCoroutineContext =
+ mTestScope.getBackgroundScope().getCoroutineContext();
+ private final CoroutineContext mMainCoroutineContext = mTestScope.getCoroutineContext();
public NotificationTestHelper(
Context context,
@@ -169,7 +177,10 @@
mIconManager = new IconManager(
mock(CommonNotifCollection.class),
mock(LauncherApps.class),
- new IconBuilder(mContext));
+ new IconBuilder(mContext),
+ mTestScope,
+ mBgCoroutineContext,
+ mMainCoroutineContext);
NotificationContentInflater contentBinder = new NotificationContentInflater(
mock(NotifRemoteViewCache.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index b938029..9a7b8ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -10,7 +10,6 @@
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.res.R
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
import com.android.systemui.statusbar.NotificationShelf
@@ -23,7 +22,6 @@
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
-import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -38,7 +36,6 @@
@RunWithLooper
open class NotificationShelfTest : SysuiTestCase() {
- open val useSensitiveReveal: Boolean = false
private val flags = FakeFeatureFlags()
@Mock private lateinit var largeScreenShadeInterpolator: LargeScreenShadeInterpolator
@@ -53,7 +50,6 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
mDependency.injectTestDependency(FeatureFlags::class.java, flags)
- flags.set(Flags.SENSITIVE_REVEAL_ANIM, useSensitiveReveal)
val root = FrameLayout(context)
shelf =
LayoutInflater.from(root.context)
@@ -335,7 +331,6 @@
@Test
fun updateState_withNullLastVisibleBackgroundChild_hideShelf() {
// GIVEN
- assumeTrue(useSensitiveReveal)
whenever(ambientState.stackY).thenReturn(100f)
whenever(ambientState.stackHeight).thenReturn(100f)
val paddingBetweenElements =
@@ -362,7 +357,6 @@
@Test
fun updateState_withNullFirstViewInShelf_hideShelf() {
// GIVEN
- assumeTrue(useSensitiveReveal)
whenever(ambientState.stackY).thenReturn(100f)
whenever(ambientState.stackHeight).thenReturn(100f)
val paddingBetweenElements =
@@ -389,7 +383,6 @@
@Test
fun updateState_withCollapsedShade_hideShelf() {
// GIVEN
- assumeTrue(useSensitiveReveal)
whenever(ambientState.stackY).thenReturn(100f)
whenever(ambientState.stackHeight).thenReturn(100f)
val paddingBetweenElements =
@@ -416,7 +409,6 @@
@Test
fun updateState_withHiddenSectionBeforeShelf_hideShelf() {
// GIVEN
- assumeTrue(useSensitiveReveal)
whenever(ambientState.stackY).thenReturn(100f)
whenever(ambientState.stackHeight).thenReturn(100f)
val paddingBetweenElements =
@@ -476,10 +468,3 @@
assertEquals(expectedAlpha, shelf.viewState.alpha)
}
}
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@RunWithLooper
-class NotificationShelfWithSensitiveRevealTest : NotificationShelfTest() {
- override val useSensitiveReveal: Boolean = true
-}
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 220305c..13df091 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
@@ -165,8 +165,6 @@
// TODO: Ideally we wouldn't need to set these unless a test actually reads them,
// and then we would test both configurations, but currently they are all read
// in the constructor.
- mFeatureFlags.setDefault(Flags.SENSITIVE_REVEAL_ANIM);
- mFeatureFlags.setDefault(Flags.ANIMATED_NOTIFICATION_SHADE_INSETS);
mSetFlagsRule.enableFlags(FLAG_NEW_AOD_TRANSITION);
mFeatureFlags.setDefault(Flags.UNCLEARED_TRANSIENT_HUN_FIX);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index 56fc7b9..1748cff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -22,6 +22,7 @@
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.android.systemui.Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND;
import static com.google.common.truth.Truth.assertThat;
@@ -38,6 +39,8 @@
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -226,7 +229,22 @@
}
@Test
- public void onViewAttached_callbacksRegistered() {
+ @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+ public void onViewAttached_updateUserSwitcherFlagEnabled_callbacksRegistered() {
+ mController.onViewAttached();
+
+ runAllScheduled();
+ verify(mConfigurationController).addCallback(any());
+ verify(mAnimationScheduler).addCallback(any());
+ verify(mUserInfoController).addCallback(any());
+ verify(mCommandQueue).addCallback(any());
+ verify(mStatusBarIconController).addIconGroup(any());
+ verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
+ }
+
+ @Test
+ @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+ public void onViewAttached_updateUserSwitcherFlagDisabled_callbacksRegistered() {
mController.onViewAttached();
verify(mConfigurationController).addCallback(any());
@@ -238,7 +256,26 @@
}
@Test
- public void onConfigurationChanged_updatesUserSwitcherVisibility() {
+ @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+ public void
+ onConfigurationChanged_updateUserSwitcherFlagEnabled_updatesUserSwitcherVisibility() {
+ mController.onViewAttached();
+ runAllScheduled();
+ verify(mConfigurationController).addCallback(mConfigurationListenerCaptor.capture());
+ clearInvocations(mUserManager);
+ clearInvocations(mKeyguardStatusBarView);
+
+ mConfigurationListenerCaptor.getValue().onConfigChanged(null);
+
+ runAllScheduled();
+ verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
+ verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
+ }
+
+ @Test
+ @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+ public void
+ onConfigurationChanged_updateUserSwitcherFlagDisabled_updatesUserSwitcherVisibility() {
mController.onViewAttached();
verify(mConfigurationController).addCallback(mConfigurationListenerCaptor.capture());
clearInvocations(mUserManager);
@@ -250,7 +287,26 @@
}
@Test
- public void onKeyguardVisibilityChanged_updatesUserSwitcherVisibility() {
+ @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+ public void
+ onKeyguardVisibilityChanged_userSwitcherFlagEnabled_updatesUserSwitcherVisibility() {
+ mController.onViewAttached();
+ runAllScheduled();
+ verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardCallbackCaptor.capture());
+ clearInvocations(mUserManager);
+ clearInvocations(mKeyguardStatusBarView);
+
+ mKeyguardCallbackCaptor.getValue().onKeyguardVisibilityChanged(true);
+
+ runAllScheduled();
+ verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
+ verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
+ }
+
+ @Test
+ @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+ public void
+ onKeyguardVisibilityChanged_userSwitcherFlagDisabled_updatesUserSwitcherVisibility() {
mController.onViewAttached();
verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardCallbackCaptor.capture());
clearInvocations(mUserManager);
@@ -298,7 +354,7 @@
verify(mStatusBarIconController).addIconGroup(any());
}
-
+
@Test
public void setBatteryListening_true_callbackAdded() {
mController.setBatteryListening(true);
@@ -762,6 +818,11 @@
return captor.getValue();
}
+ private void runAllScheduled() {
+ mBackgroundExecutor.runAllReady();
+ mFakeExecutor.runAllReady();
+ }
+
private static class TestShadeViewStateProvider
implements ShadeViewStateProvider {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index cdbbc93..f947640 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -747,14 +747,16 @@
// Open the bouncer.
mScrimController.setRawPanelExpansionFraction(0f);
+ when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_VISIBLE);
finishAnimationsImmediately();
- // Only behind widget is visible.
+ // Only behind scrim is visible.
assertScrimAlpha(Map.of(
mScrimInFront, TRANSPARENT,
mNotificationsScrim, TRANSPARENT,
mScrimBehind, OPAQUE));
+ assertScrimTint(mScrimBehind, mSurfaceColor);
// Bouncer is closed.
mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
@@ -773,8 +775,9 @@
mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB);
// Open the shade.
- mScrimController.transitionTo(SHADE_LOCKED);
mScrimController.setQsPosition(1f, 0);
+ mScrimController.setRawPanelExpansionFraction(1);
+ mScrimController.setTransitionToFullShadeProgress(1, 0);
finishAnimationsImmediately();
// Shade scrims are visible.
@@ -782,8 +785,10 @@
mNotificationsScrim, OPAQUE,
mScrimInFront, TRANSPARENT,
mScrimBehind, OPAQUE));
+ assertScrimTint(mScrimBehind, Color.BLACK);
+ assertScrimTint(mNotificationsScrim, Color.TRANSPARENT);
- mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB);
+ mScrimController.setTransitionToFullShadeProgress(0, 0);
finishAnimationsImmediately();
// All scrims are transparent.
@@ -813,14 +818,16 @@
// Open the bouncer.
mScrimController.setRawPanelExpansionFraction(0f);
+ when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_VISIBLE);
finishAnimationsImmediately();
- // Only behind widget is visible.
+ // Only behind scrim is visible.
assertScrimAlpha(Map.of(
mScrimInFront, TRANSPARENT,
mNotificationsScrim, TRANSPARENT,
mScrimBehind, OPAQUE));
+ assertScrimTint(mScrimBehind, mSurfaceColor);
// Bouncer is closed.
mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
@@ -839,7 +846,6 @@
mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
// Open the shade.
- mScrimController.transitionTo(SHADE_LOCKED);
mScrimController.setQsPosition(1f, 0);
mScrimController.setRawPanelExpansionFraction(1f);
finishAnimationsImmediately();
@@ -849,8 +855,11 @@
mNotificationsScrim, OPAQUE,
mScrimInFront, TRANSPARENT,
mScrimBehind, OPAQUE));
+ assertScrimTint(mScrimBehind, Color.BLACK);
+ assertScrimTint(mNotificationsScrim, Color.TRANSPARENT);
- mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
+ mScrimController.setQsPosition(0f, 0);
+ mScrimController.setRawPanelExpansionFraction(0f);
finishAnimationsImmediately();
// All scrims are transparent.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index b0404a0..a8c5fc3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -50,6 +50,7 @@
import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -296,6 +297,7 @@
mStatusBarNotificationPresenter = new StatusBarNotificationPresenter(
mContext,
shadeViewController,
+ mock(PanelExpansionInteractor.class),
mock(QuickSettingsController.class),
mock(HeadsUpManager.class),
notificationShadeWindowView,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 13167b2..c259782 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -71,7 +71,6 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -455,7 +454,6 @@
private RemoteInputViewController bindController(
RemoteInputView view,
NotificationEntry entry) {
- mFeatureFlags.set(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION, true);
RemoteInputViewControllerImpl viewController = new RemoteInputViewControllerImpl(
view,
entry,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 974e396..5206db4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -95,6 +95,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -292,6 +293,7 @@
assertEquals(VolumeDialogImpl.PROGRESS_HAPTICS_DISABLED, type);
}
+ @Ignore("Causing breakages so ignoring to resolve, b/329099861")
@Test
@EnableFlags(FLAG_HAPTIC_VOLUME_SLIDER)
public void testVolumeChange_withSliderHaptics_deliversOnProgressChangedHapticsEagerly() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt
index 6dd8d07..0660d00 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt
@@ -18,6 +18,7 @@
import com.android.systemui.classifier.FakeClassifierModule
import com.android.systemui.data.FakeSystemUiDataLayerModule
import com.android.systemui.flags.FakeFeatureFlagsClassicModule
+import com.android.systemui.flags.FakeSystemPropertiesHelperModule
import com.android.systemui.log.FakeUiEventLoggerModule
import com.android.systemui.settings.FakeSettingsModule
import com.android.systemui.statusbar.policy.FakeConfigurationControllerModule
@@ -33,6 +34,7 @@
FakeConfigurationControllerModule::class,
FakeExecutorModule::class,
FakeFeatureFlagsClassicModule::class,
+ FakeSystemPropertiesHelperModule::class,
FakeSettingsModule::class,
FakeSplitShadeStateControllerModule::class,
FakeSystemClockModule::class,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java
index 3428553..8001604 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java
@@ -30,6 +30,7 @@
*
* To use:
* - locally edit your test class to inherit from MemoryTrackingTestCase instead of SysuiTestCase
+ * - Use `atest -d` to prevent files being cleaned up
* - Watch the logcat with tag MEMORY to see the path to the .ahprof file
* - adb pull /path/to/something.ahprof
* - Download ahat from https://sites.google.com/corp/google.com/ahat/home
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
index 69b769e..bc0bf9d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
@@ -27,6 +27,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigModule
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.SystemUIDeviceEntryFaceAuthInteractor
import com.android.systemui.scene.SceneContainerFrameworkModule
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.scene.shared.model.SceneDataSource
@@ -56,6 +59,7 @@
CoroutineTestScopeModule::class,
FakeSystemUiModule::class,
SceneContainerFrameworkModule::class,
+ FaceWakeUpTriggersConfigModule::class,
]
)
interface SysUITestModule {
@@ -69,6 +73,11 @@
@Binds @SysUISingleton fun bindsShadeInteractor(sii: ShadeInteractorImpl): ShadeInteractor
@Binds fun bindSceneDataSource(delegator: SceneDataSourceDelegator): SceneDataSource
+ @Binds
+ fun provideFaceAuthInteractor(
+ sysUIFaceAuthInteractor: SystemUIDeviceEntryFaceAuthInteractor
+ ): DeviceEntryFaceAuthInteractor
+
companion object {
@Provides
fun provideSysuiTestableContext(test: SysuiTestCase): SysuiTestableContext = test.context
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
index 62a1aa9..3d84291 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
@@ -17,6 +17,7 @@
import android.app.ActivityManager
import android.app.admin.DevicePolicyManager
+import android.app.trust.TrustManager
import android.os.UserManager
import android.service.notification.NotificationListenerService
import android.util.DisplayMetrics
@@ -27,6 +28,7 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardViewController
import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
@@ -36,6 +38,7 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.dagger.BiometricLog
import com.android.systemui.log.dagger.BroadcastDispatcherLog
+import com.android.systemui.log.dagger.FaceAuthLog
import com.android.systemui.log.dagger.SceneFrameworkLog
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.model.SysUiState
@@ -65,10 +68,12 @@
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.policy.ZenModeController
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.settings.GlobalSettings
import com.android.wm.shell.bubbles.Bubbles
import dagger.Binds
import dagger.Module
@@ -123,6 +128,10 @@
@get:Provides val deviceEntryIconTransitions: Set<DeviceEntryIconTransition> = emptySet(),
@get:Provides val communalInteractor: CommunalInteractor = mock(),
@get:Provides val sceneLogger: SceneLogger = mock(),
+ @get:Provides val trustManager: TrustManager = mock(),
+ @get:Provides val primaryBouncerInteractor: PrimaryBouncerInteractor = mock(),
+ @get:Provides val keyguardStateController: KeyguardStateController = mock(),
+ @get:Provides val globalSettings: GlobalSettings = mock(),
// log buffers
@get:[Provides BroadcastDispatcherLog]
@@ -131,6 +140,8 @@
val sceneLogBuffer: LogBuffer = mock(),
@get:[Provides BiometricLog]
val biometricLogger: LogBuffer = mock(),
+ @get:[Provides FaceAuthLog]
+ val faceAuthLogger: LogBuffer = mock(),
@get:Provides val lsShadeTransitionLogger: LSShadeTransitionLogger = mock(),
// framework mocks
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
index 68ef555..8a95136 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
@@ -19,10 +19,15 @@
import android.graphics.Point
import com.android.systemui.biometrics.shared.model.LockoutMode
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
-class FakeFacePropertyRepository : FacePropertyRepository {
+@SysUISingleton
+class FakeFacePropertyRepository @Inject constructor() : FacePropertyRepository {
private val faceSensorInfo = MutableStateFlow<FaceSensorInfo?>(null)
override val sensorInfo: StateFlow<FaceSensorInfo?>
get() = faceSensorInfo
@@ -56,3 +61,8 @@
currentCameraInfo.value = value
}
}
+
+@Module
+interface FakeFacePropertyRepositoryModule {
+ @Binds fun bindFake(fake: FakeFacePropertyRepository): FacePropertyRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
index bd30fb4..60d61ac 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
@@ -68,6 +68,16 @@
)
}
+ /** setProperties as if the device supports POWER_BUTTON fingerprint sensor. */
+ fun supportsSideFps() {
+ setProperties(
+ sensorId = 0,
+ strength = SensorStrength.STRONG,
+ sensorType = FingerprintSensorType.POWER_BUTTON,
+ sensorLocations = emptyMap(),
+ )
+ }
+
/** setProperties as if the device supports the rear fingerprint sensor. */
fun supportsRearFps() {
setProperties(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
index c3af437..2e2cf9a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
@@ -78,6 +78,7 @@
val hasCredentialViewShown = kind.value !is PromptKind.Biometric
val showBpForCredential =
Flags.customBiometricPrompt() &&
+ com.android.systemui.Flags.constraintBp() &&
!Utils.isBiometricAllowed(promptInfo) &&
Utils.isDeviceCredentialAllowed(promptInfo) &&
promptInfo.contentView != null
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 6ac702e..8866fd3 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
@@ -16,6 +16,8 @@
package com.android.systemui.communal.domain.interactor
+import android.os.userManager
+import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.communal.data.repository.communalMediaRepository
import com.android.systemui.communal.data.repository.communalPrefsRepository
import com.android.systemui.communal.data.repository.communalRepository
@@ -29,6 +31,7 @@
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.plugins.activityStarter
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.settings.userTracker
@@ -39,6 +42,7 @@
val Kosmos.communalInteractor by Fixture {
CommunalInteractor(
applicationScope = applicationCoroutineScope,
+ broadcastDispatcher = broadcastDispatcher,
communalRepository = communalRepository,
widgetRepository = communalWidgetRepository,
mediaRepository = communalMediaRepository,
@@ -48,6 +52,8 @@
keyguardInteractor = keyguardInteractor,
editWidgetsActivityStarter = editWidgetsActivityStarter,
userTracker = userTracker,
+ activityStarter = activityStarter,
+ userManager = userManager,
logBuffer = logcatLogBuffer("CommunalInteractor"),
tableLogBuffer = mock(),
communalSettingsInteractor = communalSettingsInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
index 1c8190e..fbb8ea2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.deviceentry.data
import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepositoryModule
+import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepositoryModule
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepositoryModule
import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepositoryModule
import com.android.systemui.display.data.repository.FakeDisplayRepositoryModule
@@ -35,6 +36,7 @@
FakeDisplayRepositoryModule::class,
FakeDisplayStateRepositoryModule::class,
FakeFingerprintPropertyRepositoryModule::class,
+ FakeFacePropertyRepositoryModule::class,
FakeTrustRepositoryModule::class,
]
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt
index 66c6f86..ebed922 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt
@@ -18,6 +18,7 @@
package com.android.systemui.deviceentry.domain.interactor
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.kosmos.Kosmos
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -25,6 +26,8 @@
val Kosmos.deviceEntryFingerprintAuthInteractor by
Kosmos.Fixture {
DeviceEntryFingerprintAuthInteractor(
+ biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
repository = deviceEntryFingerprintAuthRepository,
+ fingerprintPropertyRepository = fingerprintPropertyRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
index 0d1a31f..e73e295 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
@@ -18,8 +18,8 @@
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
-import com.android.systemui.keyguard.data.repository.deviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.data.repository.trustRepository
+import com.android.systemui.flags.fakeSystemPropertiesHelper
+import com.android.systemui.keyguard.domain.interactor.trustInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -34,9 +34,12 @@
repository = deviceEntryRepository,
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
- deviceEntryFaceAuthRepository = deviceEntryFaceAuthRepository,
- trustRepository = trustRepository,
+ faceAuthInteractor = deviceEntryFaceAuthInteractor,
+ trustInteractor = trustInteractor,
flags = sceneContainerFlags,
deviceUnlockedInteractor = deviceUnlockedInteractor,
+ fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
+ biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
+ systemPropertiesHelper = fakeSystemPropertiesHelper,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeSystemPropertiesHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeSystemPropertiesHelper.kt
new file mode 100644
index 0000000..2f30d34
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeSystemPropertiesHelper.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.flags
+
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+
+@SysUISingleton
+class FakeSystemPropertiesHelper @Inject constructor() : SystemPropertiesHelper() {
+ private val fakeProperties = mutableMapOf<String, Any>()
+
+ override fun get(name: String): String {
+ return fakeProperties[name] as String
+ }
+
+ override fun get(name: String, def: String?): String {
+ return checkNotNull(fakeProperties[name] as String? ?: def)
+ }
+
+ override fun getBoolean(name: String, default: Boolean): Boolean {
+ return fakeProperties[name] as Boolean? ?: default
+ }
+
+ override fun setBoolean(name: String, value: Boolean) {
+ fakeProperties[name] = value
+ }
+
+ override fun set(name: String, value: String) {
+ fakeProperties[name] = value
+ }
+
+ override fun set(name: String, value: Int) {
+ fakeProperties[name] = value
+ }
+
+ override fun erase(name: String) {
+ fakeProperties.remove(name)
+ }
+}
+
+@Module
+interface FakeSystemPropertiesHelperModule {
+ @Binds fun bindFake(fake: FakeSystemPropertiesHelper): SystemPropertiesHelper
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
index 365d97f..d6f2f77 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
@@ -62,6 +62,7 @@
}
val Kosmos.systemPropertiesHelper by Kosmos.Fixture { SystemPropertiesHelper() }
+val Kosmos.fakeSystemPropertiesHelper by Kosmos.Fixture { FakeSystemPropertiesHelper() }
var Kosmos.serverFlagReader: ServerFlagReader by Kosmos.Fixture { serverFlagReaderFake }
val Kosmos.serverFlagReaderFake by Kosmos.Fixture { ServerFlagReaderFake() }
var Kosmos.restarter: Restarter by Kosmos.Fixture { mock() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt
index 5dd5073..a45b269 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt
@@ -19,12 +19,10 @@
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testDispatcher
val Kosmos.glanceableHubTransitions by
Kosmos.Fixture {
GlanceableHubTransitions(
- bgDispatcher = testDispatcher,
transitionRepository = keyguardTransitionRepository,
transitionInteractor = keyguardTransitionInteractor,
communalInteractor = communalInteractor,
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/TrustInteractorKosmos.kt
similarity index 65%
copy from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/TrustInteractorKosmos.kt
index 98a9e93..0ebf164 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/TrustInteractorKosmos.kt
@@ -14,16 +14,10 @@
* limitations under the License.
*/
-package com.android.credentialmanager.ui.screens
+package com.android.systemui.keyguard.domain.interactor
-import androidx.activity.result.IntentSenderRequest
+import com.android.systemui.keyguard.data.repository.trustRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
-sealed class UiState {
- data object CredentialScreen : UiState()
-
- data class CredentialSelected(
- val intentSenderRequest: IntentSenderRequest?
- ) : UiState()
-
- data object Cancel : UiState()
-}
+val Kosmos.trustInteractor by Fixture { TrustInteractor(repository = trustRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelKosmos.kt
index 00741eb..298c70d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.keyguard.domain.interactor.fromDreamingTransitionInteractor
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
@@ -25,5 +26,6 @@
DreamingToGlanceableHubTransitionViewModel(
configurationInteractor = configurationInteractor,
animationFlow = keyguardTransitionAnimationFlow,
+ fromDreamingTransitionInteractor = fromDreamingTransitionInteractor
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt
index 5f70a2f..450dcc2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt
@@ -16,7 +16,6 @@
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -26,7 +25,6 @@
@ExperimentalCoroutinesApi
val Kosmos.dreamingToLockscreenTransitionViewModel by Fixture {
DreamingToLockscreenTransitionViewModel(
- keyguardTransitionInteractor = keyguardTransitionInteractor,
fromDreamingTransitionInteractor = mock(),
animationFlow = keyguardTransitionAnimationFlow,
)
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 0b13858..b34681a 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
@@ -2,6 +2,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
@@ -9,3 +10,7 @@
var Kosmos.testScope by Fixture { TestScope(testDispatcher) }
var Kosmos.applicationCoroutineScope by Fixture { testScope.backgroundScope }
var Kosmos.testCase: SysuiTestCase by Fixture()
+var Kosmos.backgroundCoroutineContext: CoroutineContext by Fixture {
+ testScope.backgroundScope.coroutineContext
+}
+var Kosmos.mainCoroutineContext: CoroutineContext by Fixture { testScope.coroutineContext }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
index 6b604e1..728c67a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
@@ -18,6 +18,7 @@
package com.android.systemui.shade.data.repository
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.shared.model.ShadeMode
import dagger.Binds
import dagger.Module
import javax.inject.Inject
@@ -60,8 +61,8 @@
override val legacyLockscreenShadeTracking = MutableStateFlow(false)
- private val _isSplitShade = MutableStateFlow(false)
- override val isSplitShade: StateFlow<Boolean> = _isSplitShade.asStateFlow()
+ private val _shadeMode = MutableStateFlow<ShadeMode>(ShadeMode.Single)
+ override val shadeMode: StateFlow<ShadeMode> = _shadeMode.asStateFlow()
@Deprecated("Use ShadeInteractor instead")
override fun setLegacyIsQsExpanded(legacyIsQsExpanded: Boolean) {
@@ -136,8 +137,8 @@
_legacyShadeExpansion.value = expandedFraction
}
- override fun setSplitShade(isSplitShade: Boolean) {
- _isSplitShade.value = isSplitShade
+ override fun setShadeMode(shadeMode: ShadeMode) {
+ _shadeMode.value = shadeMode
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorKosmos.kt
index 2a4dd3a..09c8f87 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorKosmos.kt
@@ -23,6 +23,8 @@
val Kosmos.panelExpansionInteractor by Fixture { panelExpansionInteractorImpl }
val Kosmos.panelExpansionInteractorImpl by Fixture {
PanelExpansionInteractorImpl(
- sceneInteractor = sceneInteractor,
+ sceneInteractor,
+ shadeInteractor,
+ shadeAnimationInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt
index d2dd200..6d24e2a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shade.domain.interactor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.data.repository.shadeAnimationRepository
@@ -24,5 +25,9 @@
Kosmos.Fixture { ShadeAnimationInteractorEmptyImpl(shadeAnimationRepository) }
var Kosmos.shadeAnimationInteractorSceneContainerImpl: ShadeAnimationInteractorSceneContainerImpl by
Kosmos.Fixture {
- ShadeAnimationInteractorSceneContainerImpl(shadeAnimationRepository, sceneInteractor)
+ ShadeAnimationInteractorSceneContainerImpl(
+ testScope.backgroundScope,
+ shadeAnimationRepository,
+ sceneInteractor
+ )
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt
index d3a8e0c..0950f04 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt
@@ -18,7 +18,19 @@
import android.content.pm.launcherApps
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.backgroundCoroutineContext
+import com.android.systemui.kosmos.mainCoroutineContext
import com.android.systemui.statusbar.notification.collection.notifcollection.commonNotifCollection
val Kosmos.iconManager by
- Kosmos.Fixture { IconManager(commonNotifCollection, launcherApps, iconBuilder) }
+ Kosmos.Fixture {
+ IconManager(
+ commonNotifCollection,
+ launcherApps,
+ iconBuilder,
+ applicationCoroutineScope,
+ backgroundCoroutineContext,
+ mainCoroutineContext,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index c013664..de0cc65 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
-import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.dump.dumpManager
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
@@ -56,7 +55,6 @@
keyguardInteractor = keyguardInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
shadeInteractor = shadeInteractor,
- communalInteractor = communalInteractor,
alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
diff --git a/packages/Tethering/OWNERS b/packages/Tethering/OWNERS
deleted file mode 100644
index aa87958..0000000
--- a/packages/Tethering/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /services/core/java/com/android/server/net/OWNERS
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 3239175..004f37c 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -528,7 +528,7 @@
*/
public static Pair<PreviewExtenderImpl, ImageCaptureExtenderImpl> initializeExtension(
int extensionType) {
- if (Flags.concertMode()) {
+ if (Flags.concertModeApi()) {
if (extensionType == CameraExtensionCharacteristics.EXTENSION_EYES_FREE_VIDEOGRAPHY) {
// Basic extensions are deprecated starting with extension version 1.5
return new Pair<>(new PreviewExtenderImpl() {
@@ -713,7 +713,7 @@
* @hide
*/
public static AdvancedExtenderImpl initializeAdvancedExtensionImpl(int extensionType) {
- if (Flags.concertMode()) {
+ if (Flags.concertModeApi()) {
if (extensionType == CameraExtensionCharacteristics.EXTENSION_EYES_FREE_VIDEOGRAPHY) {
if (EFV_SUPPORTED) {
return new EyesFreeVideographyAdvancedExtenderImpl();
diff --git a/ravenwood/README.md b/ravenwood/README.md
index 9c4fda7..8cafb43 100644
--- a/ravenwood/README.md
+++ b/ravenwood/README.md
@@ -1,9 +1,11 @@
# Ravenwood
-Ravenwood is an officially-supported lightweight unit testing environment for Android platform code that runs on the host.
+Ravenwood is a lightweight unit testing environment for Android platform code that runs on the host.
Ravenwood’s focus on Android platform use-cases, improved maintainability, and device consistency distinguishes it from Robolectric, which remains a popular choice for app testing.
+> **Note:** Active development of Ravenwood has been paused as of March 2024. Existing Ravenwood tests will continue running, but support has moved to a self-service model.
+
## Background
Executing tests on a typical Android device has substantial overhead, such as flashing the build, waiting for the boot to complete, and retrying tests that fail due to general flakiness.
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index af47ed2..73584154 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -611,12 +611,12 @@
if (svcConnTracingEnabled()) {
logTraceSvcConn("getWindow", "windowId=" + windowId);
}
+ int displayId = Display.INVALID_DISPLAY;
+ if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
+ displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowId(
+ mSystemSupport.getCurrentUserIdLocked(), windowId);
+ }
synchronized (mLock) {
- int displayId = Display.INVALID_DISPLAY;
- if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
- displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowIdLocked(
- mSystemSupport.getCurrentUserIdLocked(), windowId);
- }
ensureWindowsAvailableTimedLocked(displayId);
if (!hasRightsToCurrentUserLocked()) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 3cbfd42..4be303a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1261,15 +1261,14 @@
// the computation for performance reasons.
boolean shouldComputeWindows = false;
int displayId = event.getDisplayId();
+ final int windowId = event.getWindowId();
+ if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID
+ && displayId == Display.INVALID_DISPLAY) {
+ displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowId(
+ resolvedUserId, windowId);
+ event.setDisplayId(displayId);
+ }
synchronized (mLock) {
- final int windowId = event.getWindowId();
- if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID
- && displayId == Display.INVALID_DISPLAY) {
- displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowIdLocked(
- resolvedUserId, windowId);
- event.setDisplayId(displayId);
- }
-
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
&& displayId != Display.INVALID_DISPLAY
&& mA11yWindowManager.isTrackingWindowsLocked(displayId)) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index b818150..8c06bc8 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -2038,8 +2038,11 @@
* @param windowId The windowId
* @return The display ID
*/
- public int getDisplayIdByUserIdAndWindowIdLocked(int userId, int windowId) {
- final IBinder windowToken = getWindowTokenForUserAndWindowIdLocked(userId, windowId);
+ public int getDisplayIdByUserIdAndWindowId(int userId, int windowId) {
+ final IBinder windowToken;
+ synchronized (mLock) {
+ windowToken = getWindowTokenForUserAndWindowIdLocked(userId, windowId);
+ }
if (traceWMEnabled()) {
logTraceWM("getDisplayIdForWindow", "token=" + windowToken);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
index d9e25ef..e13994e 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
@@ -525,8 +525,9 @@
mReceivedPointersDown |= pointerFlag;
mReceivedPointers[pointerId].set(
event.getX(pointerIndex), event.getY(pointerIndex), event.getEventTime());
-
- mPrimaryPointerId = pointerId;
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mPrimaryPointerId = pointerId;
+ }
}
/**
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 1749ee3..16fe007 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -4025,8 +4025,9 @@
}
}
}
- throw new IllegalArgumentException(
- providerComponent + " is not a valid AppWidget provider");
+ // Either the provider does not exist or the caller does not have permission to access its
+ // previews.
+ return null;
}
@Override
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 13bc772..a55b8d0 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -171,6 +171,7 @@
import android.util.TimeUtils;
import android.view.KeyEvent;
import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillFeatureFlags;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillManager.AutofillCommitReason;
import android.view.autofill.AutofillManager.SmartSuggestionMode;
@@ -1498,7 +1499,7 @@
mSessionCommittedEventLogger.maybeSetComponentPackageUid(uid);
mSaveEventLogger = SaveEventLogger.forSessionId(sessionId);
mIsPrimaryCredential = isPrimaryCredential;
- mIgnoreViewStateResetToEmpty = Flags.ignoreViewStateResetToEmpty();
+ mIgnoreViewStateResetToEmpty = AutofillFeatureFlags.shouldIgnoreViewStateResetToEmpty();
synchronized (mLock) {
mSessionFlags = new SessionFlags();
@@ -6458,12 +6459,12 @@
} else if (response != null) {
if (viewId.isVirtualInt()) {
ViewNode viewNode = getViewNodeFromContextsLocked(viewId);
- if (viewNode != null && viewNode.getCredentialManagerCallback() != null) {
+ if (viewNode != null && viewNode.getPendingCredentialCallback() != null) {
Bundle resultData = new Bundle();
resultData.putParcelable(
CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
response);
- viewNode.getCredentialManagerCallback().send(SUCCESS_CREDMAN_SELECTOR,
+ viewNode.getPendingCredentialCallback().send(SUCCESS_CREDMAN_SELECTOR,
resultData);
} else {
Slog.w(TAG, "View node not found after GetCredentialResponse");
diff --git a/services/backup/BACKUP_OWNERS b/services/backup/BACKUP_OWNERS
index f8f4f4f..29ae202 100644
--- a/services/backup/BACKUP_OWNERS
+++ b/services/backup/BACKUP_OWNERS
@@ -2,9 +2,10 @@
jstemmer@google.com
martinoh@google.com
-millmore@google.com
niamhfw@google.com
piee@google.com
philippov@google.com
rthakohov@google.com
-sarpm@google.com
\ No newline at end of file
+sarpm@google.com
+beatricemarch@google.com
+azilio@google.com
\ No newline at end of file
diff --git a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
index 82ab098..340bc32 100644
--- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
+++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
@@ -38,7 +38,8 @@
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
- ]
+ ],
+ "keywords": ["primary-device"]
},
{
"name": "CtsHardwareTestCases",
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index b1672ed..8244d20 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -29,6 +29,7 @@
import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+import static android.companion.virtualdevice.flags.Flags.virtualCameraServiceDiscovery;
import android.annotation.EnforcePermission;
import android.annotation.NonNull;
@@ -111,6 +112,8 @@
import com.android.server.companion.virtual.camera.VirtualCameraController;
import com.android.server.inputmethod.InputMethodManagerInternal;
+import dalvik.annotation.optimization.FastNative;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.List;
@@ -265,7 +268,7 @@
runningAppsChangedCallback,
params,
DisplayManagerGlobal.getInstance(),
- Flags.virtualCamera()
+ isVirtualCameraEnabled()
? new VirtualCameraController(params.getDevicePolicy(POLICY_TYPE_CAMERA))
: null);
}
@@ -1535,4 +1538,13 @@
return mToken;
}
}
+
+ private static boolean isVirtualCameraEnabled() {
+ return Flags.virtualCamera() && virtualCameraServiceDiscovery()
+ && nativeVirtualCameraServiceBuildFlagEnabled();
+ }
+
+ // Returns true if virtual_camera service is enabled in this build.
+ @FastNative
+ private static native boolean nativeVirtualCameraServiceBuildFlagEnabled();
}
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index 7d8aad7..ecd14ce 100644
--- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -24,6 +24,7 @@
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.media.projection.MediaProjectionInfo;
import android.media.projection.MediaProjectionManager;
@@ -79,7 +80,7 @@
Trace.beginSection(
"SensitiveContentProtectionManagerService.onProjectionStart");
try {
- onProjectionStart(info);
+ onProjectionStart(info.getPackageName());
} finally {
Trace.endSection();
}
@@ -124,14 +125,6 @@
}
}
- // These packages are exempted from screen share protection.
- private ArraySet<String> getExemptedPackages() {
- final ArraySet<String> exemptedPackages =
- SystemConfig.getInstance().getBugreportWhitelistedPackages();
- // TODO(b/323361046) - Add sys ui recorder package.
- return exemptedPackages;
- }
-
@VisibleForTesting
void init(MediaProjectionManager projectionManager, WindowManagerInternal windowManager,
ArraySet<String> exemptedPackages) {
@@ -179,9 +172,22 @@
}
}
- private void onProjectionStart(MediaProjectionInfo info) {
- if (mExemptedPackages != null && mExemptedPackages.contains(info.getPackageName())) {
- Log.w(TAG, info.getPackageName() + " is exempted from screen share protection.");
+ private boolean canRecordSensitiveContent(@NonNull String packageName) {
+ return getContext().getPackageManager()
+ .checkPermission(android.Manifest.permission.RECORD_SENSITIVE_CONTENT,
+ packageName) == PackageManager.PERMISSION_GRANTED;
+ }
+
+ // These packages are exempted from screen share protection.
+ private ArraySet<String> getExemptedPackages() {
+ return SystemConfig.getInstance().getBugreportWhitelistedPackages();
+ }
+
+ private void onProjectionStart(String packageName) {
+ // exempt on device screen recorder as well.
+ if ((mExemptedPackages != null && mExemptedPackages.contains(packageName))
+ || canRecordSensitiveContent(packageName)) {
+ Log.w(TAG, packageName + " is exempted from screen share protection.");
return;
}
// TODO(b/324447419): move GlobalSettings lookup to background thread
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 130a733..1334a95 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -113,6 +113,7 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
+import com.android.modules.expresslog.Histogram;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.SystemService;
@@ -284,6 +285,11 @@
private static AtomicReference<AccountManagerService> sThis = new AtomicReference<>();
private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[]{};
+ private static Histogram sResponseLatency = new Histogram(
+ "app.value_high_authenticator_response_latency",
+ new Histogram.ScaledRangeOptions(20, 10000, 10000, 1.5f)
+ );
+
/**
* This should only be called by system code. One should only call this after the service
* has started.
@@ -4937,6 +4943,9 @@
protected boolean mCanStartAccountManagerActivity = false;
protected final UserAccounts mAccounts;
+ private int mAuthenticatorUid;
+ private long mBindingStartTime;
+
public Session(UserAccounts accounts, IAccountManagerResponse response, String accountType,
boolean expectActivityLaunch, boolean stripAuthTokenFromResult, String accountName,
boolean authDetailsRequired) {
@@ -4974,6 +4983,10 @@
}
IAccountManagerResponse getResponseAndClose() {
+ if (mAuthenticatorUid != 0 && mBindingStartTime > 0) {
+ sResponseLatency.logSampleWithUid(mAuthenticatorUid,
+ SystemClock.uptimeMillis() - mBindingStartTime);
+ }
if (mResponse == null) {
close();
return null;
@@ -5353,7 +5366,8 @@
mContext.unbindService(this);
return false;
}
-
+ mAuthenticatorUid = authenticatorInfo.uid;
+ mBindingStartTime = SystemClock.uptimeMillis();
return true;
}
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 5298846..0012b3d 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -204,6 +204,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
import android.service.voice.HotwordDetectionService;
import android.service.voice.VisualQueryDetectionService;
import android.service.wearable.WearableSensingService;
@@ -4518,13 +4519,14 @@
}
// TODO(b/265746493): Special case for HotwordDetectionService,
- // VisualQueryDetectionService and WearableSensingService.
+ // VisualQueryDetectionService, WearableSensingService and OnDeviceSandboxedInferenceService
// Need a cleaner way to append this seInfo.
private String generateAdditionalSeInfoFromService(Intent service) {
if (service != null && service.getAction() != null
&& (service.getAction().equals(HotwordDetectionService.SERVICE_INTERFACE)
|| service.getAction().equals(VisualQueryDetectionService.SERVICE_INTERFACE)
- || service.getAction().equals(WearableSensingService.SERVICE_INTERFACE))) {
+ || service.getAction().equals(WearableSensingService.SERVICE_INTERFACE)
+ || service.getAction().equals(OnDeviceSandboxedInferenceService.SERVICE_INTERFACE))) {
return ":isolatedComputeApp";
}
return "";
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 60bfc63..447dfd9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -4755,6 +4755,7 @@
autofillOptions,
contentCaptureOptions,
app.getDisabledCompatChanges(),
+ app.getLoggableCompatChanges(),
serializedSystemFontMap,
app.getStartElapsedTime(),
app.getStartUptime());
@@ -9010,7 +9011,7 @@
// cleaning up the old proxies.
VMRuntime.getRuntime().requestConcurrentGC();
}
- }, BackgroundThread.getHandler());
+ }, mHandler);
t.traceEnd(); // setBinderProxies
t.traceEnd(); // ActivityManagerStartApps
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 0ce1407..48daef8 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -711,7 +711,7 @@
}
}
if (profile != null) {
- long startTime = SystemClock.currentThreadTimeMillis();
+ long startTime = SystemClock.uptimeMillis();
// skip background PSS calculation under the following situations:
// - app is capturing camera imagery
// - app is frozen and we have already collected PSS once.
@@ -721,7 +721,7 @@
|| mService.isCameraActiveForUid(profile.mApp.uid)
|| mService.mConstants.APP_PROFILER_PSS_PROFILING_DISABLED;
long pss = skipPSSCollection ? 0 : Debug.getPss(pid, tmp, null);
- long endTime = SystemClock.currentThreadTimeMillis();
+ long endTime = SystemClock.uptimeMillis();
synchronized (mProfilerLock) {
if (pss != 0 && profile.getThread() != null
&& profile.getSetProcState() == procState
@@ -852,7 +852,7 @@
}
}
if (profile != null) {
- long startTime = SystemClock.currentThreadTimeMillis();
+ long startTime = SystemClock.uptimeMillis();
// skip background RSS calculation under the following situations:
// - app is capturing camera imagery
// - app is frozen and we have already collected RSS once.
@@ -862,7 +862,7 @@
|| mService.isCameraActiveForUid(profile.mApp.uid)
|| mService.mConstants.APP_PROFILER_PSS_PROFILING_DISABLED;
long rss = skipRSSCollection ? 0 : Debug.getRss(pid, null);
- long endTime = SystemClock.currentThreadTimeMillis();
+ long endTime = SystemClock.uptimeMillis();
synchronized (mProfilerLock) {
if (rss != 0 && profile.getThread() != null
&& profile.getSetProcState() == procState
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 5521381..0a6e9d3 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -286,9 +286,6 @@
// when the flag is fused on.
private static final int MSG_DELIVERY_TIMEOUT_SOFT = 8;
- // TODO: Use the trunk stable flag.
- private static final boolean DEFER_FROZEN_OUTGOING_BCASTS = false;
-
private void enqueueUpdateRunningList() {
mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
mLocalHandler.sendEmptyMessage(MSG_UPDATE_RUNNING_LIST);
@@ -766,7 +763,7 @@
// TODO: Apply delivery group policies and FLAG_REPLACE_PENDING to collapse the
// outgoing broadcasts.
// TODO: Add traces/logs for the enqueueing outgoing broadcasts logic.
- if (DEFER_FROZEN_OUTGOING_BCASTS && isProcessFreezable(r.callerApp)) {
+ if (Flags.deferOutgoingBcasts() && isProcessFreezable(r.callerApp)) {
final BroadcastProcessQueue queue = getOrCreateProcessQueue(
r.callerApp.processName, r.callerApp.uid);
if (queue.getOutgoingBroadcastCount() >= mConstants.MAX_FROZEN_OUTGOING_BROADCASTS) {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index cd6964e..7f6d62c 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1051,7 +1051,7 @@
assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
- postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime);
+ postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime, true);
if (startProfiling) {
mService.mOomAdjProfiler.oomAdjEnded();
@@ -1073,12 +1073,12 @@
@GuardedBy({"mService", "mProcLock"})
protected void postUpdateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, ActiveUids activeUids,
- long now, long nowElapsed, long oldTime) {
+ long now, long nowElapsed, long oldTime, boolean doingAll) {
mNumNonCachedProcs = 0;
mNumCachedHiddenProcs = 0;
final boolean allChanged = updateAndTrimProcessLSP(now, nowElapsed, oldTime, activeUids,
- oomAdjReason);
+ oomAdjReason, doingAll);
mNumServiceProcs = mNewNumServiceProcs;
if (mService.mAlwaysFinishActivities) {
@@ -1288,7 +1288,8 @@
@GuardedBy({"mService", "mProcLock"})
private boolean updateAndTrimProcessLSP(final long now, final long nowElapsed,
- final long oldTime, final ActiveUids activeUids, @OomAdjReason int oomAdjReason) {
+ final long oldTime, final ActiveUids activeUids, @OomAdjReason int oomAdjReason,
+ boolean doingAll) {
ArrayList<ProcessRecord> lruList = mProcessList.getLruProcessesLOSP();
final int numLru = lruList.size();
@@ -1321,7 +1322,7 @@
if (!app.isKilledByAm() && app.getThread() != null) {
// We don't need to apply the update for the process which didn't get computed
if (state.getCompletedAdjSeq() == mAdjSeq) {
- applyOomAdjLSP(app, true, now, nowElapsed, oomAdjReason);
+ applyOomAdjLSP(app, doingAll, now, nowElapsed, oomAdjReason);
}
if (app.isPendingFinishAttach()) {
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index dd75bc0..46bdfe8 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -820,7 +820,7 @@
computeConnectionsLSP();
assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
- postUpdateOomAdjInnerLSP(oomAdjReason, mActiveUids, now, nowElapsed, oldTime);
+ postUpdateOomAdjInnerLSP(oomAdjReason, mActiveUids, now, nowElapsed, oldTime, true);
}
/**
@@ -908,7 +908,7 @@
}
}
- postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime);
+ postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime, false);
}
/**
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 27d6c60..48a9d6a 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2088,8 +2088,10 @@
+ " with non-zero pid:" + app.getPid());
}
app.setDisabledCompatChanges(null);
+ app.setLoggableCompatChanges(null);
if (mPlatformCompat != null) {
app.setDisabledCompatChanges(mPlatformCompat.getDisabledChanges(app.info));
+ app.setLoggableCompatChanges(mPlatformCompat.getLoggableChanges(app.info));
}
final long startSeq = ++mProcStartSeqCounter;
app.setStartSeq(startSeq);
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 9fa3a8b..b939089 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -259,6 +259,12 @@
private long[] mDisabledCompatChanges;
/**
+ * Set of compat changes for the process that are intended to be logged to logcat.
+ */
+ @GuardedBy("mService")
+ private long[] mLoggableCompatChanges;
+
+ /**
* Who is watching for the death.
*/
@GuardedBy("mService")
@@ -935,11 +941,21 @@
}
@GuardedBy("mService")
+ long[] getLoggableCompatChanges() {
+ return mLoggableCompatChanges;
+ }
+
+ @GuardedBy("mService")
void setDisabledCompatChanges(long[] disabledCompatChanges) {
mDisabledCompatChanges = disabledCompatChanges;
}
@GuardedBy("mService")
+ void setLoggableCompatChanges(long[] loggableCompatChanges) {
+ mLoggableCompatChanges = loggableCompatChanges;
+ }
+
+ @GuardedBy("mService")
void unlinkDeathRecipient() {
if (mDeathRecipient != null && mThread != null) {
mThread.asBinder().unlinkToDeath(mDeathRecipient, 0);
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index d1bda79..7df5fdd 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -170,6 +170,7 @@
"pixel_connectivity_gps",
"pixel_system_sw_video",
"pixel_watch",
+ "platform_compat",
"platform_security",
"pmw",
"power",
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index e0c2425..14428c4 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -157,7 +157,7 @@
* corresponding peers in case of BLE
*/
void addAudioDeviceInInventoryIfNeeded(int deviceType, String address, String peerAddress,
- @AudioDeviceCategory int category) {
+ @AudioDeviceCategory int category, boolean userDefined) {
if (!isBluetoothOutDevice(deviceType)) {
return;
}
@@ -167,7 +167,11 @@
ads = findBtDeviceStateForAddress(peerAddress, deviceType);
}
if (ads != null) {
- if (ads.getAudioDeviceCategory() != category) {
+ // if category is user defined allow to change back to unknown otherwise
+ // do not reset the category back to unknown since it might have been set
+ // before by the user
+ if (ads.getAudioDeviceCategory() != category && (userDefined
+ || category != AUDIO_DEVICE_CATEGORY_UNKNOWN)) {
ads.setAudioDeviceCategory(category);
mDeviceBroker.postUpdatedAdiDeviceState(ads);
mDeviceBroker.postPersistAudioDeviceSettings();
@@ -220,9 +224,9 @@
void addAudioDeviceWithCategoryInInventoryIfNeeded(@NonNull String address,
@AudioDeviceCategory int btAudioDeviceCategory) {
addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_BLE_HEADSET,
- address, "", btAudioDeviceCategory);
+ address, "", btAudioDeviceCategory, /*userDefined=*/true);
addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_BLUETOOTH_A2DP,
- address, "", btAudioDeviceCategory);
+ address, "", btAudioDeviceCategory, /*userDefined=*/true);
}
@AudioDeviceCategory
@@ -1733,7 +1737,7 @@
purgeDevicesRoles_l();
} else {
addAudioDeviceInInventoryIfNeeded(device, address, "",
- BtHelper.getBtDeviceCategory(address));
+ BtHelper.getBtDeviceCategory(address), /*userDefined=*/false);
}
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"SCO " + (AudioSystem.isInputDevice(device) ? "source" : "sink")
@@ -2023,7 +2027,7 @@
updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/);
addAudioDeviceInInventoryIfNeeded(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, "",
- BtHelper.getBtDeviceCategory(address));
+ BtHelper.getBtDeviceCategory(address), /*userDefined=*/false);
}
static final int[] CAPTURE_PRESETS = new int[] {AudioSource.MIC, AudioSource.CAMCORDER,
@@ -2357,7 +2361,7 @@
DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable");
setCurrentAudioRouteNameIfPossible(name, false /*fromA2dp*/);
addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_HEARING_AID, address, "",
- BtHelper.getBtDeviceCategory(address));
+ BtHelper.getBtDeviceCategory(address), /*userDefined=*/false);
new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceAvailable")
.set(MediaMetrics.Property.ADDRESS, address != null ? address : "")
.set(MediaMetrics.Property.DEVICE,
@@ -2488,7 +2492,7 @@
mDeviceBroker.postAccessoryPlugMediaUnmute(device);
setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false);
addAudioDeviceInInventoryIfNeeded(device, address, peerAddress,
- BtHelper.getBtDeviceCategory(address));
+ BtHelper.getBtDeviceCategory(address), /*userDefined=*/false);
}
if (streamType == AudioSystem.STREAM_DEFAULT) {
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 48bf9f4..e915688 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -791,14 +791,12 @@
private void registerAuthenticators() {
BiometricHandlerProvider handlerProvider = mInjector.getBiometricHandlerProvider();
- handlerProvider.getFingerprintHandler().post(() ->
- registerFingerprintSensors(mInjector.getFingerprintAidlInstances(),
- mInjector.getFingerprintConfiguration(getContext()), getContext(),
- mInjector.getFingerprintService()));
- handlerProvider.getFaceHandler().post(() ->
- registerFaceSensors(mInjector.getFaceAidlInstances(),
- mInjector.getFaceConfiguration(getContext()), getContext(),
- mInjector.getFaceService()));
+ registerFingerprintSensors(mInjector.getFingerprintAidlInstances(),
+ mInjector.getFingerprintConfiguration(getContext()), getContext(),
+ mInjector.getFingerprintService(), handlerProvider);
+ registerFaceSensors(mInjector.getFaceAidlInstances(),
+ mInjector.getFaceConfiguration(getContext()), getContext(),
+ mInjector.getFaceService(), handlerProvider);
registerIrisSensors(mInjector.getIrisConfiguration(getContext()));
}
@@ -854,30 +852,38 @@
*/
private static void registerFaceSensors(final String[] faceAidlInstances,
final String[] hidlConfigStrings, final Context context,
- final IFaceService faceService) {
- final FaceSensorConfigurations mFaceSensorConfigurations =
- new FaceSensorConfigurations(hidlConfigStrings != null
- && hidlConfigStrings.length > 0);
-
- if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
- mFaceSensorConfigurations.addHidlConfigs(hidlConfigStrings, context);
+ final IFaceService faceService, final BiometricHandlerProvider handlerProvider) {
+ if ((hidlConfigStrings == null || hidlConfigStrings.length == 0)
+ && (faceAidlInstances == null || faceAidlInstances.length == 0)) {
+ Slog.d(TAG, "No face sensors.");
+ return;
}
- if (faceAidlInstances != null && faceAidlInstances.length > 0) {
- mFaceSensorConfigurations.addAidlConfigs(faceAidlInstances,
- name -> IFace.Stub.asInterface(Binder.allowBlocking(
- ServiceManager.waitForDeclaredService(name))));
- }
+ handlerProvider.getFaceHandler().post(() -> {
+ final FaceSensorConfigurations mFaceSensorConfigurations =
+ new FaceSensorConfigurations(hidlConfigStrings != null
+ && hidlConfigStrings.length > 0);
- if (faceService != null) {
- try {
- faceService.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
- } catch (RemoteException e) {
- Slog.e(TAG, "RemoteException when registering face authenticators", e);
+ if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
+ mFaceSensorConfigurations.addHidlConfigs(hidlConfigStrings, context);
}
- } else if (mFaceSensorConfigurations.hasSensorConfigurations()) {
- Slog.e(TAG, "Face configuration exists, but FaceService is null.");
- }
+
+ if (faceAidlInstances != null && faceAidlInstances.length > 0) {
+ mFaceSensorConfigurations.addAidlConfigs(faceAidlInstances,
+ name -> IFace.Stub.asInterface(Binder.allowBlocking(
+ ServiceManager.waitForDeclaredService(name))));
+ }
+
+ if (faceService != null) {
+ try {
+ faceService.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException when registering face authenticators", e);
+ }
+ } else if (mFaceSensorConfigurations.hasSensorConfigurations()) {
+ Slog.e(TAG, "Face configuration exists, but FaceService is null.");
+ }
+ });
}
/**
@@ -885,30 +891,40 @@
*/
private static void registerFingerprintSensors(final String[] fingerprintAidlInstances,
final String[] hidlConfigStrings, final Context context,
- final IFingerprintService fingerprintService) {
- final FingerprintSensorConfigurations mFingerprintSensorConfigurations =
- new FingerprintSensorConfigurations(!(hidlConfigStrings != null
- && hidlConfigStrings.length > 0));
-
- if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
- mFingerprintSensorConfigurations.addHidlSensors(hidlConfigStrings, context);
+ final IFingerprintService fingerprintService,
+ final BiometricHandlerProvider handlerProvider) {
+ if ((hidlConfigStrings == null || hidlConfigStrings.length == 0)
+ && (fingerprintAidlInstances == null || fingerprintAidlInstances.length == 0)) {
+ Slog.d(TAG, "No fingerprint sensors.");
+ return;
}
- if (fingerprintAidlInstances != null && fingerprintAidlInstances.length > 0) {
- mFingerprintSensorConfigurations.addAidlSensors(fingerprintAidlInstances,
- name -> IFingerprint.Stub.asInterface(Binder.allowBlocking(
- ServiceManager.waitForDeclaredService(name))));
- }
+ handlerProvider.getFingerprintHandler().post(() -> {
+ final FingerprintSensorConfigurations mFingerprintSensorConfigurations =
+ new FingerprintSensorConfigurations(!(hidlConfigStrings != null
+ && hidlConfigStrings.length > 0));
- if (fingerprintService != null) {
- try {
- fingerprintService.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
- } catch (RemoteException e) {
- Slog.e(TAG, "RemoteException when registering fingerprint authenticators", e);
+ if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
+ mFingerprintSensorConfigurations.addHidlSensors(hidlConfigStrings, context);
}
- } else if (mFingerprintSensorConfigurations.hasSensorConfigurations()) {
- Slog.e(TAG, "Fingerprint configuration exists, but FingerprintService is null.");
- }
+
+ if (fingerprintAidlInstances != null && fingerprintAidlInstances.length > 0) {
+ mFingerprintSensorConfigurations.addAidlSensors(fingerprintAidlInstances,
+ name -> IFingerprint.Stub.asInterface(Binder.allowBlocking(
+ ServiceManager.waitForDeclaredService(name))));
+ }
+
+ if (fingerprintService != null) {
+ try {
+ fingerprintService.registerAuthenticatorsLegacy(
+ mFingerprintSensorConfigurations);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException when registering fingerprint authenticators", e);
+ }
+ } else if (mFingerprintSensorConfigurations.hasSensorConfigurations()) {
+ Slog.e(TAG, "Fingerprint configuration exists, but FingerprintService is null.");
+ }
+ });
}
/**
diff --git a/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java b/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
index a923daa..e578861 100644
--- a/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
+++ b/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
@@ -16,6 +16,9 @@
package com.android.server.biometrics;
+import static android.os.Process.THREAD_PRIORITY_DEFAULT;
+import static android.os.Process.THREAD_PRIORITY_DISPLAY;
+
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
@@ -27,9 +30,9 @@
private static final BiometricHandlerProvider sBiometricHandlerProvider =
new BiometricHandlerProvider();
- private final Handler mBiometricsCallbackHandler;
- private final Handler mFingerprintHandler;
- private final Handler mFaceHandler;
+ private Handler mBiometricsCallbackHandler;
+ private Handler mFingerprintHandler;
+ private Handler mFaceHandler;
/**
* @return an instance of {@link BiometricHandlerProvider} which contains the three
@@ -39,16 +42,16 @@
return sBiometricHandlerProvider;
}
- private BiometricHandlerProvider() {
- mBiometricsCallbackHandler = getNewHandler("BiometricsCallbackHandler");
- mFingerprintHandler = getNewHandler("FingerprintHandler");
- mFaceHandler = getNewHandler("FaceHandler");
- }
+ private BiometricHandlerProvider() {}
/**
* @return the handler to process all biometric callback operations
*/
public synchronized Handler getBiometricCallbackHandler() {
+ if (mBiometricsCallbackHandler == null) {
+ mBiometricsCallbackHandler = getNewHandler("BiometricsCallbackHandler",
+ THREAD_PRIORITY_DISPLAY);
+ }
return mBiometricsCallbackHandler;
}
@@ -56,6 +59,9 @@
* @return the handler to process all face related biometric operations
*/
public synchronized Handler getFaceHandler() {
+ if (mFaceHandler == null) {
+ mFaceHandler = getNewHandler("FaceHandler", THREAD_PRIORITY_DEFAULT);
+ }
return mFaceHandler;
}
@@ -63,12 +69,15 @@
* @return the handler to process all fingerprint related biometric operations
*/
public synchronized Handler getFingerprintHandler() {
+ if (mFingerprintHandler == null) {
+ mFingerprintHandler = getNewHandler("FingerprintHandler", THREAD_PRIORITY_DEFAULT);
+ }
return mFingerprintHandler;
}
- private Handler getNewHandler(String tag) {
+ private Handler getNewHandler(String tag, int priority) {
if (Flags.deHidl()) {
- HandlerThread handlerThread = new HandlerThread(tag);
+ HandlerThread handlerThread = new HandlerThread(tag, priority);
handlerThread.start();
return new Handler(handlerThread.getLooper());
}
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
index 03acf72..d93ff9d 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
@@ -82,13 +82,6 @@
Slogf.w(TAG, "No module %s with id %d (HAL AIDL)", name, moduleId);
return;
}
- try {
- radioModule.setInternalHalCallback();
- } catch (RemoteException ex) {
- Slogf.wtf(TAG, ex, "Broadcast radio module %s with id %d (HAL AIDL) "
- + "cannot register HAL callback", name, moduleId);
- return;
- }
if (DEBUG) {
Slogf.d(TAG, "Loaded broadcast radio module %s with id %d (HAL AIDL)",
name, moduleId);
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
index 4b3444d..cd86510 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
@@ -246,10 +246,6 @@
return mProperties;
}
- void setInternalHalCallback() throws RemoteException {
- mService.setTunerCallback(mHalTunerCallback);
- }
-
TunerSession openSession(android.hardware.radio.ITunerCallback userCb)
throws RemoteException {
mLogger.logRadioEvent("Open TunerSession");
@@ -257,10 +253,14 @@
Boolean antennaConnected;
RadioManager.ProgramInfo currentProgramInfo;
synchronized (mLock) {
+ boolean isFirstTunerSession = mAidlTunerSessions.isEmpty();
tunerSession = new TunerSession(this, mService, userCb);
mAidlTunerSessions.add(tunerSession);
antennaConnected = mAntennaConnected;
currentProgramInfo = mCurrentProgramInfo;
+ if (isFirstTunerSession) {
+ mService.setTunerCallback(mHalTunerCallback);
+ }
}
// Propagate state to new client.
// Note: These callbacks are invoked while holding mLock to prevent race conditions
@@ -284,7 +284,6 @@
synchronized (mLock) {
tunerSessions = new TunerSession[mAidlTunerSessions.size()];
mAidlTunerSessions.toArray(tunerSessions);
- mAidlTunerSessions.clear();
}
for (TunerSession tunerSession : tunerSessions) {
@@ -402,6 +401,14 @@
mAidlTunerSessions.remove(tunerSession);
}
onTunerSessionProgramListFilterChanged(null);
+ if (mAidlTunerSessions.isEmpty()) {
+ try {
+ mService.unsetTunerCallback();
+ } catch (RemoteException ex) {
+ Slogf.wtf(TAG, ex, "Failed to unregister HAL callback for module %d",
+ mProperties.getId());
+ }
+ }
}
// add to mHandler queue
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index 9102cfd..79025d0 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -25,6 +25,7 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.os.Build;
import android.os.Environment;
import android.text.TextUtils;
import android.util.LongArray;
@@ -72,7 +73,6 @@
* been configured.
*/
final class CompatConfig {
-
private static final String TAG = "CompatConfig";
private static final String APP_COMPAT_DATA_DIR = "/data/misc/appcompat";
private static final String STATIC_OVERRIDES_PRODUCT_DIR = "/product/etc/appcompat";
@@ -149,6 +149,56 @@
}
/**
+ * Retrieves the set of changes that are intended to be logged. This includes changes that
+ * target the most recent SDK version and are not disabled.
+ *
+ * @param app the app in question
+ * @return a sorted long array of change IDs
+ */
+ long[] getLoggableChanges(ApplicationInfo app) {
+ LongArray loggable = new LongArray(mChanges.size());
+ for (CompatChange c : mChanges.values()) {
+ long changeId = c.getId();
+ boolean isLatestSdk = isChangeTargetingLatestSdk(c, app.targetSdkVersion);
+ if (c.isEnabled(app, mAndroidBuildClassifier) && isLatestSdk) {
+ loggable.add(changeId);
+ }
+ }
+ final long[] sortedChanges = loggable.toArray();
+ Arrays.sort(sortedChanges);
+ return sortedChanges;
+ }
+
+ /**
+ * Whether the change indicated by the given changeId is targeting the latest SDK version.
+ * @param c the change for which to check the target SDK version
+ * @param appSdkVersion the target sdk version of the app
+ * @return true if the changeId targets the current sdk version or the current development
+ * version.
+ */
+ boolean isChangeTargetingLatestSdk(CompatChange c, int appSdkVersion) {
+ int maxTargetSdk = maxTargetSdkForCompatChange(c) + 1;
+ if (maxTargetSdk <= 0) {
+ // No max target sdk found.
+ return false;
+ }
+
+ return maxTargetSdk == Build.VERSION_CODES.CUR_DEVELOPMENT || maxTargetSdk == appSdkVersion;
+ }
+
+ /**
+ * Retrieves the CompatChange associated with the given changeId. Will return null if the
+ * changeId is not found. Used only for performance improvement purposes, in order to reduce
+ * lookups.
+ *
+ * @param changeId for which to look up the CompatChange
+ * @return the found compat change, or null if not found.
+ */
+ CompatChange getCompatChange(long changeId) {
+ return mChanges.get(changeId);
+ }
+
+ /**
* Looks up a change ID by name.
*
* @param name name of the change to look up
@@ -164,7 +214,7 @@
}
/**
- * Checks if a given change is enabled for a given application.
+ * Checks if a given change id is enabled for a given application.
*
* @param changeId the ID of the change in question
* @param app app to check for
@@ -173,6 +223,18 @@
*/
boolean isChangeEnabled(long changeId, ApplicationInfo app) {
CompatChange c = mChanges.get(changeId);
+ return isChangeEnabled(c, app);
+ }
+
+ /**
+ * Checks if a given change is enabled for a given application.
+ *
+ * @param c the CompatChange in question
+ * @param app the app to check for
+ * @return {@code true} if the change is enabled for this app. Also returns {@code true} if the
+ * change ID is not known, as unknown changes are enabled by default.
+ */
+ boolean isChangeEnabled(CompatChange c, ApplicationInfo app) {
if (c == null) {
// we know nothing about this change: default behaviour is enabled.
return true;
@@ -301,9 +363,21 @@
/**
* Returns the maximum SDK version for which this change can be opted in (or -1 if it is not
* target SDK gated).
+ *
+ * @param changeId the id of the CompatChange to check for the max target sdk
*/
int maxTargetSdkForChangeIdOptIn(long changeId) {
CompatChange c = mChanges.get(changeId);
+ return maxTargetSdkForCompatChange(c);
+ }
+
+ /**
+ * Returns the maximum SDK version for which this change can be opted in (or -1 if it is not
+ * target SDK gated).
+ *
+ * @param c the CompatChange to check for the max target sdk
+ */
+ int maxTargetSdkForCompatChange(CompatChange c) {
if (c != null && c.getEnableSinceTargetSdk() != -1) {
return c.getEnableSinceTargetSdk() - 1;
}
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 6cca130..f8fd0a0 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -120,8 +120,16 @@
reportChangeInternal(changeId, uid, ChangeReporter.STATE_LOGGED);
}
+ /**
+ * Report the change, but skip over the sdk target version check. This can be used to force the
+ * debug logs.
+ *
+ * @param changeId of the change to report
+ * @param uid of the user
+ * @param state of the change - enabled/disabled/logged
+ */
private void reportChangeInternal(long changeId, int uid, int state) {
- mChangeReporter.reportChange(uid, changeId, state);
+ mChangeReporter.reportChange(uid, changeId, state, true);
}
@Override
@@ -164,15 +172,25 @@
}
/**
- * Internal version of {@link #isChangeEnabled(long, ApplicationInfo)}.
+ * Internal version of {@link #isChangeEnabled(long, ApplicationInfo)}. If the provided appInfo
+ * is not null, also reports the change.
+ *
+ * @param changeId of the change to report
+ * @param appInfo the app to check
*
* <p>Does not perform costly permission check.
*/
public boolean isChangeEnabledInternal(long changeId, ApplicationInfo appInfo) {
- boolean enabled = isChangeEnabledInternalNoLogging(changeId, appInfo);
+ // Fetch the CompatChange. This is done here instead of in mCompatConfig to avoid multiple
+ // fetches.
+ CompatChange c = mCompatConfig.getCompatChange(changeId);
+
+ boolean enabled = mCompatConfig.isChangeEnabled(c, appInfo);
+ int state = enabled ? ChangeReporter.STATE_ENABLED : ChangeReporter.STATE_DISABLED;
if (appInfo != null) {
- reportChangeInternal(changeId, appInfo.uid,
- enabled ? ChangeReporter.STATE_ENABLED : ChangeReporter.STATE_DISABLED);
+ boolean isTargetingLatestSdk =
+ mCompatConfig.isChangeTargetingLatestSdk(c, appInfo.targetSdkVersion);
+ mChangeReporter.reportChange(appInfo.uid, changeId, state, isTargetingLatestSdk);
}
return enabled;
}
@@ -399,6 +417,19 @@
}
/**
+ * Retrieves the set of changes that should be logged for a given app. Any change ID not in the
+ * returned array is ignored for logging purposes.
+ *
+ * @param appInfo The app in question
+ * @return A sorted long array of change IDs. We use a primitive array to minimize memory
+ * footprint: Every app process will store this array statically so we aim to reduce
+ * overhead as much as possible.
+ */
+ public long[] getLoggableChanges(ApplicationInfo appInfo) {
+ return mCompatConfig.getLoggableChanges(appInfo);
+ }
+
+ /**
* Look up a change ID by name.
*
* @param name Name of the change to look up
diff --git a/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java b/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java
new file mode 100644
index 0000000..133c79f
--- /dev/null
+++ b/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.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 com.android.server.crashrecovery;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
+import android.net.ConnectivityModuleConnector;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.server.PackageWatchdog;
+import com.android.server.pm.ApexManager;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Provides helper methods for the CrashRecovery APEX
+ *
+ * @hide
+ */
+public final class CrashRecoveryHelper {
+ private static final String TAG = "CrashRecoveryHelper";
+
+ private final ApexManager mApexManager;
+ private final Context mContext;
+ private final ConnectivityModuleConnector mConnectivityModuleConnector;
+
+
+ /** @hide */
+ public CrashRecoveryHelper(@NonNull Context context) {
+ mContext = context;
+ mApexManager = ApexManager.getInstance();
+ mConnectivityModuleConnector = ConnectivityModuleConnector.getInstance();
+ }
+
+ /**
+ * Returns true if the package name is the name of a module.
+ * If the package is an APK inside an APEX then it will use the parent's APEX package name
+ * do determine if it is a module or not.
+ * @hide
+ */
+ @AnyThread
+ public boolean isModule(@NonNull String packageName) {
+ String apexPackageName =
+ mApexManager.getActiveApexPackageNameContainingPackage(packageName);
+ if (apexPackageName != null) {
+ packageName = apexPackageName;
+ }
+
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ return pm.getModuleInfo(packageName, 0) != null;
+ } catch (PackageManager.NameNotFoundException ignore) {
+ return false;
+ }
+ }
+
+ /**
+ * Register health listeners for explicit package failures.
+ * Currently only registering for Connectivity Module health.
+ * @hide
+ */
+ public void registerConnectivityModuleHealthListener(@NonNull int failureReason) {
+ // register listener for ConnectivityModule
+ mConnectivityModuleConnector.registerHealthListener(
+ packageName -> {
+ final VersionedPackage pkg = getVersionedPackage(packageName);
+ if (pkg == null) {
+ Slog.wtf(TAG, "NetworkStack failed but could not find its package");
+ return;
+ }
+ final List<VersionedPackage> pkgList = Collections.singletonList(pkg);
+ PackageWatchdog.getInstance(mContext).onPackageFailure(pkgList, failureReason);
+ });
+ }
+
+ @Nullable
+ private VersionedPackage getVersionedPackage(String packageName) {
+ final PackageManager pm = mContext.getPackageManager();
+ if (pm == null || TextUtils.isEmpty(packageName)) {
+ return null;
+ }
+ try {
+ final long versionCode = getPackageInfo(packageName).getLongVersionCode();
+ return new VersionedPackage(packageName, versionCode);
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Gets PackageInfo for the given package. Matches any user and apex.
+ *
+ * @throws PackageManager.NameNotFoundException if no such package is installed.
+ */
+ private PackageInfo getPackageInfo(String packageName)
+ throws PackageManager.NameNotFoundException {
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ // The MATCH_ANY_USER flag doesn't mix well with the MATCH_APEX
+ // flag, so make two separate attempts to get the package info.
+ // We don't need both flags at the same time because we assume
+ // apex files are always installed for all users.
+ return pm.getPackageInfo(packageName, PackageManager.MATCH_ANY_USER);
+ } catch (PackageManager.NameNotFoundException e) {
+ return pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/crashrecovery/OWNERS b/services/core/java/com/android/server/crashrecovery/OWNERS
new file mode 100644
index 0000000..daa0211
--- /dev/null
+++ b/services/core/java/com/android/server/crashrecovery/OWNERS
@@ -0,0 +1,3 @@
+ancr@google.com
+harshitmahajan@google.com
+robertogil@google.com
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 3b05b47..a7748f4 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -44,6 +44,15 @@
* </p>
*/
abstract class DisplayDevice {
+ /**
+ * Maximum acceptable anisotropy for the output image.
+ *
+ * Necessary to avoid unnecessary scaling when pixels are almost square, as they are non ideal
+ * anyway. For external displays, we expect an anisotropy of about 2% even if the pixels
+ * are, in fact, square due to the imprecision of the display's actual size (parsed from edid
+ * and rounded to the nearest cm).
+ */
+ static final float MAX_ANISOTROPY = 1.025f;
private static final String TAG = "DisplayDevice";
private static final Display.Mode EMPTY_DISPLAY_MODE = new Display.Mode.Builder().build();
@@ -69,13 +78,21 @@
// Do not use for any other purpose.
DisplayDeviceInfo mDebugLastLoggedDeviceInfo;
- public DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId,
+ private final boolean mIsAnisotropyCorrectionEnabled;
+
+ DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId,
Context context) {
+ this(displayAdapter, displayToken, uniqueId, context, false);
+ }
+
+ DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId,
+ Context context, boolean isAnisotropyCorrectionEnabled) {
mDisplayAdapter = displayAdapter;
mDisplayToken = displayToken;
mUniqueId = uniqueId;
mDisplayDeviceConfig = null;
mContext = context;
+ mIsAnisotropyCorrectionEnabled = isAnisotropyCorrectionEnabled;
}
/**
@@ -143,8 +160,17 @@
DisplayDeviceInfo displayDeviceInfo = getDisplayDeviceInfoLocked();
final boolean isRotated = mCurrentOrientation == ROTATION_90
|| mCurrentOrientation == ROTATION_270;
- return isRotated ? new Point(displayDeviceInfo.height, displayDeviceInfo.width)
- : new Point(displayDeviceInfo.width, displayDeviceInfo.height);
+ var width = displayDeviceInfo.width;
+ var height = displayDeviceInfo.height;
+ if (mIsAnisotropyCorrectionEnabled && displayDeviceInfo.yDpi > 0
+ && displayDeviceInfo.xDpi > 0) {
+ if (displayDeviceInfo.xDpi > displayDeviceInfo.yDpi * MAX_ANISOTROPY) {
+ height = (int) (height * displayDeviceInfo.xDpi / displayDeviceInfo.yDpi + 0.5);
+ } else if (displayDeviceInfo.xDpi * MAX_ANISOTROPY < displayDeviceInfo.yDpi) {
+ width = (int) (width * displayDeviceInfo.yDpi / displayDeviceInfo.xDpi + 0.5);
+ }
+ }
+ return isRotated ? new Point(height, width) : new Point(width, height);
}
/**
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 88c24e0..b2fd9ed 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -257,7 +257,8 @@
SurfaceControl.DynamicDisplayInfo dynamicInfo,
SurfaceControl.DesiredDisplayModeSpecs modeSpecs, boolean isFirstDisplay) {
super(LocalDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + physicalDisplayId,
- getContext());
+ getContext(),
+ getFeatureFlags().isPixelAnisotropyCorrectionInLogicalDisplayEnabled());
mPhysicalDisplayId = physicalDisplayId;
mIsFirstDisplay = isFirstDisplay;
updateDisplayPropertiesLocked(staticDisplayInfo, dynamicInfo, modeSpecs);
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index db636d6..5eaaf35 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -35,7 +35,6 @@
import com.android.server.display.layout.Layout;
import com.android.server.display.mode.DisplayModeDirector;
-import com.android.server.wm.utils.DisplayInfoOverrides;
import com.android.server.wm.utils.InsetUtils;
import java.io.PrintWriter;
@@ -204,7 +203,28 @@
private SparseArray<SurfaceControl.RefreshRateRange> mThermalRefreshRateThrottling =
new SparseArray<>();
+ /**
+ * If the aspect ratio of the resolution of the display does not match the physical aspect
+ * ratio of the display, then without this feature enabled, picture would appear stretched to
+ * the user. This is because applications assume that they are rendered on square pixels
+ * (meaning density of pixels in x and y directions are equal). This would result into circles
+ * appearing as ellipses to the user.
+ * To compensate for non-square (anisotropic) pixels, if this feature is enabled:
+ * 1. LogicalDisplay will add more pixels for the applications to render on, as if the pixels
+ * were square and occupied the full display.
+ * 2. SurfaceFlinger will squeeze this taller/wider surface into the available number of
+ * physical pixels in the current display resolution.
+ * 3. If a setting on the display itself is set to "fill the entire display panel" then the
+ * display will stretch the pixels to fill the display fully.
+ */
+ private final boolean mIsAnisotropyCorrectionEnabled;
+
LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) {
+ this(displayId, layerStack, primaryDisplayDevice, false);
+ }
+
+ LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice,
+ boolean isAnisotropyCorrectionEnabled) {
mDisplayId = displayId;
mLayerStack = layerStack;
mPrimaryDisplayDevice = primaryDisplayDevice;
@@ -215,6 +235,7 @@
mThermalBrightnessThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;
mPowerThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;
mBaseDisplayInfo.thermalBrightnessThrottlingDataId = mThermalBrightnessThrottlingDataId;
+ mIsAnisotropyCorrectionEnabled = isAnisotropyCorrectionEnabled;
}
public void setDevicePositionLocked(int position) {
@@ -453,6 +474,14 @@
int maskedWidth = deviceInfo.width - maskingInsets.left - maskingInsets.right;
int maskedHeight = deviceInfo.height - maskingInsets.top - maskingInsets.bottom;
+ if (mIsAnisotropyCorrectionEnabled && deviceInfo.xDpi > 0 && deviceInfo.yDpi > 0) {
+ if (deviceInfo.xDpi > deviceInfo.yDpi * DisplayDevice.MAX_ANISOTROPY) {
+ maskedHeight = (int) (maskedHeight * deviceInfo.xDpi / deviceInfo.yDpi + 0.5);
+ } else if (deviceInfo.xDpi * DisplayDevice.MAX_ANISOTROPY < deviceInfo.yDpi) {
+ maskedWidth = (int) (maskedWidth * deviceInfo.yDpi / deviceInfo.xDpi + 0.5);
+ }
+ }
+
mBaseDisplayInfo.type = deviceInfo.type;
mBaseDisplayInfo.address = deviceInfo.address;
mBaseDisplayInfo.deviceProductInfo = deviceInfo.deviceProductInfo;
@@ -666,6 +695,31 @@
physWidth -= maskingInsets.left + maskingInsets.right;
physHeight -= maskingInsets.top + maskingInsets.bottom;
+ var displayLogicalWidth = displayInfo.logicalWidth;
+ var displayLogicalHeight = displayInfo.logicalHeight;
+
+ if (mIsAnisotropyCorrectionEnabled && displayDeviceInfo.xDpi > 0
+ && displayDeviceInfo.yDpi > 0) {
+ if (displayDeviceInfo.xDpi > displayDeviceInfo.yDpi * DisplayDevice.MAX_ANISOTROPY) {
+ var scalingFactor = displayDeviceInfo.yDpi / displayDeviceInfo.xDpi;
+ if (rotated) {
+ displayLogicalWidth = (int) ((float) displayLogicalWidth * scalingFactor + 0.5);
+ } else {
+ displayLogicalHeight = (int) ((float) displayLogicalHeight * scalingFactor
+ + 0.5);
+ }
+ } else if (displayDeviceInfo.xDpi * DisplayDevice.MAX_ANISOTROPY
+ < displayDeviceInfo.yDpi) {
+ var scalingFactor = displayDeviceInfo.xDpi / displayDeviceInfo.yDpi;
+ if (rotated) {
+ displayLogicalHeight = (int) ((float) displayLogicalHeight * scalingFactor
+ + 0.5);
+ } else {
+ displayLogicalWidth = (int) ((float) displayLogicalWidth * scalingFactor + 0.5);
+ }
+ }
+ }
+
// Determine whether the width or height is more constrained to be scaled.
// physWidth / displayInfo.logicalWidth => letter box
// or physHeight / displayInfo.logicalHeight => pillar box
@@ -675,16 +729,16 @@
// comparing them.
int displayRectWidth, displayRectHeight;
if ((displayInfo.flags & Display.FLAG_SCALING_DISABLED) != 0 || mDisplayScalingDisabled) {
- displayRectWidth = displayInfo.logicalWidth;
- displayRectHeight = displayInfo.logicalHeight;
- } else if (physWidth * displayInfo.logicalHeight
- < physHeight * displayInfo.logicalWidth) {
+ displayRectWidth = displayLogicalWidth;
+ displayRectHeight = displayLogicalHeight;
+ } else if (physWidth * displayLogicalHeight
+ < physHeight * displayLogicalWidth) {
// Letter box.
displayRectWidth = physWidth;
- displayRectHeight = displayInfo.logicalHeight * physWidth / displayInfo.logicalWidth;
+ displayRectHeight = displayLogicalHeight * physWidth / displayLogicalWidth;
} else {
// Pillar box.
- displayRectWidth = displayInfo.logicalWidth * physHeight / displayInfo.logicalHeight;
+ displayRectWidth = displayLogicalWidth * physHeight / displayLogicalHeight;
displayRectHeight = physHeight;
}
int displayRectTop = (physHeight - displayRectHeight) / 2;
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 3452e0f..e092fda 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -1151,7 +1151,8 @@
*/
private LogicalDisplay createNewLogicalDisplayLocked(DisplayDevice device, int displayId) {
final int layerStack = assignLayerStackLocked(displayId);
- final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);
+ final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device,
+ mFlags.isPixelAnisotropyCorrectionInLogicalDisplayEnabled());
display.updateLocked(mDisplayDeviceRepo);
final DisplayInfo info = display.getDisplayInfoLocked();
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 3c98ee4..15ee937 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -121,6 +121,11 @@
Flags::refreshRateVotingTelemetry
);
+ private final FlagState mPixelAnisotropyCorrectionEnabled = new FlagState(
+ Flags.FLAG_ENABLE_PIXEL_ANISOTROPY_CORRECTION,
+ Flags::enablePixelAnisotropyCorrection
+ );
+
private final FlagState mSensorBasedBrightnessThrottling = new FlagState(
Flags.FLAG_SENSOR_BASED_BRIGHTNESS_THROTTLING,
Flags::sensorBasedBrightnessThrottling
@@ -259,6 +264,10 @@
return mRefreshRateVotingTelemetry.isEnabled();
}
+ public boolean isPixelAnisotropyCorrectionInLogicalDisplayEnabled() {
+ return mPixelAnisotropyCorrectionEnabled.isEnabled();
+ }
+
public boolean isSensorBasedBrightnessThrottlingEnabled() {
return mSensorBasedBrightnessThrottling.isEnabled();
}
@@ -290,6 +299,7 @@
pw.println(" " + mAutoBrightnessModesFlagState);
pw.println(" " + mFastHdrTransitions);
pw.println(" " + mRefreshRateVotingTelemetry);
+ pw.println(" " + mPixelAnisotropyCorrectionEnabled);
pw.println(" " + mSensorBasedBrightnessThrottling);
pw.println(" " + mRefactorDisplayPowerController);
}
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 3404527..9bf36e4 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -186,6 +186,14 @@
}
flag {
+ name: "enable_pixel_anisotropy_correction"
+ namespace: "display_manager"
+ description: "Feature flag for enabling display anisotropy correction through LogicalDisplay upscaling"
+ bug: "317363416"
+ is_fixed_read_only: true
+}
+
+flag {
name: "sensor_based_brightness_throttling"
namespace: "display_manager"
description: "Feature flag for enabling brightness throttling using sensor from config."
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 8b4e1ff..64cbd54 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -1015,12 +1015,15 @@
// Infinity means that we want the highest possible refresh rate
minRefreshRate = highestRefreshRate;
- if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled) {
- // The flag had been turned off, we need to restore the original value
+ if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
+ && displayId == Display.DEFAULT_DISPLAY) {
+ // The flag has been turned off, we need to restore the original value. We'll
+ // use the peak refresh rate of the default display.
Settings.System.putFloatForUser(cr, Settings.System.MIN_REFRESH_RATE,
highestRefreshRate, cr.getUserId());
}
} else if (mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
+ && displayId == Display.DEFAULT_DISPLAY
&& Math.round(minRefreshRate) == Math.round(highestRefreshRate)) {
// The flag has been turned on, we need to upgrade the setting
Settings.System.putFloatForUser(cr, Settings.System.MIN_REFRESH_RATE,
@@ -1033,12 +1036,15 @@
// Infinity means that we want the highest possible refresh rate
peakRefreshRate = highestRefreshRate;
- if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled) {
- // The flag had been turned off, we need to restore the original value
+ if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
+ && displayId == Display.DEFAULT_DISPLAY) {
+ // The flag has been turned off, we need to restore the original value. We'll
+ // use the peak refresh rate of the default display.
Settings.System.putFloatForUser(cr, Settings.System.PEAK_REFRESH_RATE,
highestRefreshRate, cr.getUserId());
}
} else if (mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
+ && displayId == Display.DEFAULT_DISPLAY
&& Math.round(peakRefreshRate) == Math.round(highestRefreshRate)) {
// The flag has been turned on, we need to upgrade the setting
Settings.System.putFloatForUser(cr, Settings.System.PEAK_REFRESH_RATE,
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index c6d66db..d997020 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -982,6 +982,18 @@
}
@Override // Binder call
+ public boolean canStartDreaming(boolean isScreenOn) {
+ checkPermission(android.Manifest.permission.READ_DREAM_STATE);
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return canStartDreamingInternal(isScreenOn);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
public void testDream(int userId, ComponentName dream) {
if (dream == null) {
throw new IllegalArgumentException("dream must not be null");
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index 252ea4b..e6bf2c9 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -61,7 +61,6 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.InputStream;
import java.nio.charset.StandardCharsets;
/**
@@ -126,14 +125,18 @@
@Override
public void setSystemWideGrammaticalGender(int grammaticalGender, int userId) {
- checkCallerIsSystem();
+ isCallerAllowed();
GrammaticalInflectionService.this.setSystemWideGrammaticalGender(grammaticalGender,
userId);
}
@Override
public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) {
- return canGetSystemGrammaticalGender(attributionSource)
+ if (!checkSystemGrammaticalGenderPermission(mPermissionManager, attributionSource)) {
+ throw new SecurityException("AttributionSource: " + attributionSource
+ + " does not have READ_SYSTEM_GRAMMATICAL_GENDER permission.");
+ }
+ return checkSystemTermsOfAddressIsEnabled()
? GrammaticalInflectionService.this.getSystemGrammaticalGender(
attributionSource, userId)
: GRAMMATICAL_GENDER_NOT_SPECIFIED;
@@ -154,7 +157,7 @@
@Override
@Nullable
public byte[] getBackupPayload(int userId) {
- checkCallerIsSystem();
+ isCallerAllowed();
return mBackupHelper.getBackupPayload(userId);
}
@@ -333,11 +336,13 @@
return GRAMMATICAL_GENDER_NOT_SPECIFIED;
}
- private void checkCallerIsSystem() {
+ private void isCallerAllowed() {
int callingUid = Binder.getCallingUid();
if (callingUid != Process.SYSTEM_UID && callingUid != Process.SHELL_UID
&& callingUid != Process.ROOT_UID) {
- throw new SecurityException("Caller is not system, shell and root.");
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CHANGE_CONFIGURATION,
+ "Caller must be system, shell, root or has CHANGE_CONFIGURATION permission.");
}
}
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
index c7b60da..dd6433d 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
@@ -19,11 +19,13 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Handler;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.DirectBootAwareness;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
@@ -67,7 +69,7 @@
AdditionalSubtypeUtils.save(map, inputMethodMap, userId);
}
- static void initialize(@NonNull Handler handler) {
+ static void initialize(@NonNull Handler handler, @NonNull Context context) {
final UserManagerInternal userManagerInternal =
LocalServices.getService(UserManagerInternal.class);
handler.post(() -> {
@@ -79,8 +81,16 @@
handler.post(() -> {
synchronized (ImfLock.class) {
if (!sPerUserMap.contains(userId)) {
- sPerUserMap.put(userId,
- AdditionalSubtypeUtils.load(userId));
+ final AdditionalSubtypeMap additionalSubtypeMap =
+ AdditionalSubtypeUtils.load(userId);
+ sPerUserMap.put(userId, additionalSubtypeMap);
+ final InputMethodSettings settings =
+ InputMethodManagerService
+ .queryInputMethodServicesInternal(context,
+ userId,
+ additionalSubtypeMap,
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, settings);
}
}
});
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 996477d..a0e910d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -67,7 +67,6 @@
import android.annotation.Nullable;
import android.annotation.UiThread;
import android.annotation.UserIdInt;
-import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -196,21 +195,16 @@
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.security.InvalidParameterException;
-import java.time.Instant;
-import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import java.util.Locale;
import java.util.Objects;
import java.util.OptionalInt;
import java.util.WeakHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
@@ -290,6 +284,10 @@
final Context mContext;
final Resources mRes;
private final Handler mHandler;
+
+ /**
+ * TODO(b/329163064): Remove this field.
+ */
@NonNull
@MultiUserUnawareField
private InputMethodSettings mSettings;
@@ -730,344 +728,9 @@
private final CopyOnWriteArrayList<InputMethodListListener> mInputMethodListListeners =
new CopyOnWriteArrayList<>();
- /**
- * Internal state snapshot when
- * {@link IInputMethod#startInput(IInputMethod.StartInputParams)} is about to be called.
- *
- * <p>Calling that IPC endpoint basically means that
- * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called
- * back in the current IME process shortly, which will also affect what the current IME starts
- * receiving from {@link InputMethodService#getCurrentInputConnection()}. In other words, this
- * snapshot will be taken every time when {@link InputMethodManagerService} is initiating a new
- * logical input session between the client application and the current IME.</p>
- *
- * <p>Be careful to not keep strong references to this object forever, which can prevent
- * {@link StartInputInfo#mImeToken} and {@link StartInputInfo#mTargetWindow} from being GC-ed.
- * </p>
- */
- private static class StartInputInfo {
- private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
-
- final int mSequenceNumber;
- final long mTimestamp;
- final long mWallTime;
- @UserIdInt
- final int mImeUserId;
- @NonNull
- final IBinder mImeToken;
- final int mImeDisplayId;
- @NonNull
- final String mImeId;
- @StartInputReason
- final int mStartInputReason;
- final boolean mRestarting;
- @UserIdInt
- final int mTargetUserId;
- final int mTargetDisplayId;
- @Nullable
- final IBinder mTargetWindow;
- @NonNull
- final EditorInfo mEditorInfo;
- @SoftInputModeFlags
- final int mTargetWindowSoftInputMode;
- final int mClientBindSequenceNumber;
-
- StartInputInfo(@UserIdInt int imeUserId, @NonNull IBinder imeToken, int imeDisplayId,
- @NonNull String imeId, @StartInputReason int startInputReason, boolean restarting,
- @UserIdInt int targetUserId, int targetDisplayId, @Nullable IBinder targetWindow,
- @NonNull EditorInfo editorInfo, @SoftInputModeFlags int targetWindowSoftInputMode,
- int clientBindSequenceNumber) {
- mSequenceNumber = sSequenceNumber.getAndIncrement();
- mTimestamp = SystemClock.uptimeMillis();
- mWallTime = System.currentTimeMillis();
- mImeUserId = imeUserId;
- mImeToken = imeToken;
- mImeDisplayId = imeDisplayId;
- mImeId = imeId;
- mStartInputReason = startInputReason;
- mRestarting = restarting;
- mTargetUserId = targetUserId;
- mTargetDisplayId = targetDisplayId;
- mTargetWindow = targetWindow;
- mEditorInfo = editorInfo;
- mTargetWindowSoftInputMode = targetWindowSoftInputMode;
- mClientBindSequenceNumber = clientBindSequenceNumber;
- }
- }
-
@GuardedBy("ImfLock.class")
private final WeakHashMap<IBinder, IBinder> mImeTargetWindowMap = new WeakHashMap<>();
- @VisibleForTesting
- static final class SoftInputShowHideHistory {
- private final Entry[] mEntries = new Entry[16];
- private int mNextIndex = 0;
- private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
-
- static final class Entry {
- final int mSequenceNumber = sSequenceNumber.getAndIncrement();
- @Nullable
- final ClientState mClientState;
- @SoftInputModeFlags
- final int mFocusedWindowSoftInputMode;
- @SoftInputShowHideReason
- final int mReason;
- // The timing of handling showCurrentInputLocked() or hideCurrentInputLocked().
- final long mTimestamp;
- final long mWallTime;
- final boolean mInFullscreenMode;
- @NonNull
- final String mFocusedWindowName;
- @Nullable
- final EditorInfo mEditorInfo;
- @NonNull
- final String mRequestWindowName;
- @Nullable
- final String mImeControlTargetName;
- @Nullable
- final String mImeTargetNameFromWm;
- @Nullable
- final String mImeSurfaceParentName;
-
- Entry(ClientState client, EditorInfo editorInfo,
- String focusedWindowName, @SoftInputModeFlags int softInputMode,
- @SoftInputShowHideReason int reason,
- boolean inFullscreenMode, String requestWindowName,
- @Nullable String imeControlTargetName, @Nullable String imeTargetName,
- @Nullable String imeSurfaceParentName) {
- mClientState = client;
- mEditorInfo = editorInfo;
- mFocusedWindowName = focusedWindowName;
- mFocusedWindowSoftInputMode = softInputMode;
- mReason = reason;
- mTimestamp = SystemClock.uptimeMillis();
- mWallTime = System.currentTimeMillis();
- mInFullscreenMode = inFullscreenMode;
- mRequestWindowName = requestWindowName;
- mImeControlTargetName = imeControlTargetName;
- mImeTargetNameFromWm = imeTargetName;
- mImeSurfaceParentName = imeSurfaceParentName;
- }
- }
-
- void addEntry(@NonNull Entry entry) {
- final int index = mNextIndex;
- mEntries[index] = entry;
- mNextIndex = (mNextIndex + 1) % mEntries.length;
- }
-
- void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
- final DateTimeFormatter formatter =
- DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
- .withZone(ZoneId.systemDefault());
-
- for (int i = 0; i < mEntries.length; ++i) {
- final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
- if (entry == null) {
- continue;
- }
- pw.print(prefix);
- pw.println("SoftInputShowHide #" + entry.mSequenceNumber + ":");
-
- pw.print(prefix);
- pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
- + " (timestamp=" + entry.mTimestamp + ")");
-
- pw.print(prefix);
- pw.print(" reason=" + InputMethodDebug.softInputDisplayReasonToString(
- entry.mReason));
- pw.println(" inFullscreenMode=" + entry.mInFullscreenMode);
-
- pw.print(prefix);
- pw.println(" requestClient=" + entry.mClientState);
-
- pw.print(prefix);
- pw.println(" focusedWindowName=" + entry.mFocusedWindowName);
-
- pw.print(prefix);
- pw.println(" requestWindowName=" + entry.mRequestWindowName);
-
- pw.print(prefix);
- pw.println(" imeControlTargetName=" + entry.mImeControlTargetName);
-
- pw.print(prefix);
- pw.println(" imeTargetNameFromWm=" + entry.mImeTargetNameFromWm);
-
- pw.print(prefix);
- pw.println(" imeSurfaceParentName=" + entry.mImeSurfaceParentName);
-
- pw.print(prefix);
- pw.print(" editorInfo:");
- if (entry.mEditorInfo != null) {
- pw.print(" inputType=" + entry.mEditorInfo.inputType);
- pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions);
- pw.println(" fieldId (viewId)=" + entry.mEditorInfo.fieldId);
- } else {
- pw.println(" null");
- }
-
- pw.print(prefix);
- pw.println(" focusedWindowSoftInputMode=" + InputMethodDebug.softInputModeToString(
- entry.mFocusedWindowSoftInputMode));
- }
- }
- }
-
- /**
- * A ring buffer to store the history of {@link StartInputInfo}.
- */
- private static final class StartInputHistory {
- /**
- * Entry size for non low-RAM devices.
- *
- * <p>TODO: Consider to follow what other system services have been doing to manage
- * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
- */
- private static final int ENTRY_SIZE_FOR_HIGH_RAM_DEVICE = 32;
-
- /**
- * Entry size for low-RAM devices.
- *
- * <p>TODO: Consider to follow what other system services have been doing to manage
- * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
- */
- private static final int ENTRY_SIZE_FOR_LOW_RAM_DEVICE = 5;
-
- private static int getEntrySize() {
- if (ActivityManager.isLowRamDeviceStatic()) {
- return ENTRY_SIZE_FOR_LOW_RAM_DEVICE;
- } else {
- return ENTRY_SIZE_FOR_HIGH_RAM_DEVICE;
- }
- }
-
- /**
- * Backing store for the ring buffer.
- */
- private final Entry[] mEntries = new Entry[getEntrySize()];
-
- /**
- * An index of {@link #mEntries}, to which next {@link #addEntry(StartInputInfo)} should
- * write.
- */
- private int mNextIndex = 0;
-
- /**
- * Recyclable entry to store the information in {@link StartInputInfo}.
- */
- private static final class Entry {
- int mSequenceNumber;
- long mTimestamp;
- long mWallTime;
- @UserIdInt
- int mImeUserId;
- @NonNull
- String mImeTokenString;
- int mImeDisplayId;
- @NonNull
- String mImeId;
- @StartInputReason
- int mStartInputReason;
- boolean mRestarting;
- @UserIdInt
- int mTargetUserId;
- int mTargetDisplayId;
- @NonNull
- String mTargetWindowString;
- @NonNull
- EditorInfo mEditorInfo;
- @SoftInputModeFlags
- int mTargetWindowSoftInputMode;
- int mClientBindSequenceNumber;
-
- Entry(@NonNull StartInputInfo original) {
- set(original);
- }
-
- void set(@NonNull StartInputInfo original) {
- mSequenceNumber = original.mSequenceNumber;
- mTimestamp = original.mTimestamp;
- mWallTime = original.mWallTime;
- mImeUserId = original.mImeUserId;
- // Intentionally convert to String so as not to keep a strong reference to a Binder
- // object.
- mImeTokenString = String.valueOf(original.mImeToken);
- mImeDisplayId = original.mImeDisplayId;
- mImeId = original.mImeId;
- mStartInputReason = original.mStartInputReason;
- mRestarting = original.mRestarting;
- mTargetUserId = original.mTargetUserId;
- mTargetDisplayId = original.mTargetDisplayId;
- // Intentionally convert to String so as not to keep a strong reference to a Binder
- // object.
- mTargetWindowString = String.valueOf(original.mTargetWindow);
- mEditorInfo = original.mEditorInfo;
- mTargetWindowSoftInputMode = original.mTargetWindowSoftInputMode;
- mClientBindSequenceNumber = original.mClientBindSequenceNumber;
- }
- }
-
- /**
- * Add a new entry and discard the oldest entry as needed.
- * @param info {@link StartInputInfo} to be added.
- */
- void addEntry(@NonNull StartInputInfo info) {
- final int index = mNextIndex;
- if (mEntries[index] == null) {
- mEntries[index] = new Entry(info);
- } else {
- mEntries[index].set(info);
- }
- mNextIndex = (mNextIndex + 1) % mEntries.length;
- }
-
- void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
- final DateTimeFormatter formatter =
- DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
- .withZone(ZoneId.systemDefault());
-
- for (int i = 0; i < mEntries.length; ++i) {
- final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
- if (entry == null) {
- continue;
- }
- pw.print(prefix);
- pw.println("StartInput #" + entry.mSequenceNumber + ":");
-
- pw.print(prefix);
- pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
- + " (timestamp=" + entry.mTimestamp + ")"
- + " reason="
- + InputMethodDebug.startInputReasonToString(entry.mStartInputReason)
- + " restarting=" + entry.mRestarting);
-
- pw.print(prefix);
- pw.print(" imeToken=" + entry.mImeTokenString + " [" + entry.mImeId + "]");
- pw.print(" imeUserId=" + entry.mImeUserId);
- pw.println(" imeDisplayId=" + entry.mImeDisplayId);
-
- pw.print(prefix);
- pw.println(" targetWin=" + entry.mTargetWindowString
- + " [" + entry.mEditorInfo.packageName + "]"
- + " targetUserId=" + entry.mTargetUserId
- + " targetDisplayId=" + entry.mTargetDisplayId
- + " clientBindSeq=" + entry.mClientBindSequenceNumber);
-
- pw.print(prefix);
- pw.println(" softInputMode=" + InputMethodDebug.softInputModeToString(
- entry.mTargetWindowSoftInputMode));
-
- pw.print(prefix);
- pw.println(" inputType=0x" + Integer.toHexString(entry.mEditorInfo.inputType)
- + " imeOptions=0x" + Integer.toHexString(entry.mEditorInfo.imeOptions)
- + " fieldId=0x" + Integer.toHexString(entry.mEditorInfo.fieldId)
- + " fieldName=" + entry.mEditorInfo.fieldName
- + " actionId=" + entry.mEditorInfo.actionId
- + " actionLabel=" + entry.mEditorInfo.actionLabel);
- }
- }
- }
-
@GuardedBy("ImfLock.class")
@NonNull
private final StartInputHistory mStartInputHistory = new StartInputHistory();
@@ -1208,7 +871,18 @@
if (!mSystemReady) {
return;
}
- buildInputMethodListLocked(true);
+ for (int userId : mUserManagerInternal.getUserIds()) {
+ final InputMethodSettings settings = queryInputMethodServicesInternal(
+ mContext,
+ userId,
+ AdditionalSubtypeMapRepository.get(userId),
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, settings);
+ if (userId == mSettings.getUserId()) {
+ mSettings = settings;
+ }
+ }
+ postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */);
// If the locale is changed, needs to reset the default ime
resetDefaultImeLocked(mContext);
updateFromSettingsLocked(true);
@@ -1404,13 +1078,7 @@
final boolean isCurrentUser = (userId == mSettings.getUserId());
final AdditionalSubtypeMap additionalSubtypeMap =
AdditionalSubtypeMapRepository.get(userId);
- final InputMethodSettings settings;
- if (isCurrentUser) {
- settings = mSettings;
- } else {
- settings = queryInputMethodServicesInternal(mContext, userId,
- additionalSubtypeMap, DirectBootAwareness.AUTO);
- }
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
InputMethodInfo curIm = null;
String curInputMethodId = settings.getSelectedInputMethod();
@@ -1454,13 +1122,20 @@
AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
settings.getMethodMap());
}
-
- if (!isCurrentUser
- || !(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) {
+ if (isCurrentUser
+ && !(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) {
return;
}
- buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ userId, newAdditionalSubtypeMap, DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, newSettings);
+ if (!isCurrentUser) {
+ return;
+ }
+ mSettings = queryInputMethodServicesInternal(mContext, userId,
+ newAdditionalSubtypeMap, DirectBootAwareness.AUTO);
+ postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
boolean changed = false;
@@ -1612,17 +1287,20 @@
void onUnlockUser(@UserIdInt int userId) {
synchronized (ImfLock.class) {
- final int currentUserId = mSettings.getUserId();
if (DEBUG) {
- Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + currentUserId);
+ Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId="
+ + mSettings.getUserId());
}
- if (userId != currentUserId) {
+ if (!mSystemReady) {
return;
}
- mSettings = InputMethodSettings.createEmptyMap(userId);
- if (mSystemReady) {
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ userId, AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, newSettings);
+ if (mSettings.getUserId() == userId) {
+ mSettings = newSettings;
// We need to rebuild IMEs.
- buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
+ postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
}
}
@@ -1688,12 +1366,13 @@
mShowOngoingImeSwitcherForPhones = false;
- AdditionalSubtypeMapRepository.initialize(mHandler);
+ // InputMethodSettingsRepository should be initialized before buildInputMethodListLocked
+ InputMethodSettingsRepository.initialize(mHandler, mContext);
+ AdditionalSubtypeMapRepository.initialize(mHandler, mContext);
final int userId = mActivityManagerInternal.getCurrentUserId();
- // mSettings should be created before buildInputMethodListLocked
- mSettings = InputMethodSettings.createEmptyMap(userId);
+ mSettings = InputMethodSettingsRepository.get(userId);
mSwitchingController =
InputMethodSubtypeSwitchingController.createInstanceLocked(context,
@@ -1856,7 +1535,12 @@
// The mSystemReady flag is set during boot phase,
// and user switch would not happen at that time.
resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_USER);
- buildInputMethodListLocked(initialUserSwitch);
+
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ newUserId, AdditionalSubtypeMapRepository.get(newUserId), DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(newUserId, newSettings);
+ mSettings = newSettings;
+ postInputMethodSettingUpdatedLocked(initialUserSwitch /* resetDefaultEnabledIme */);
if (TextUtils.isEmpty(mSettings.getSelectedInputMethod())) {
// This is the first time of the user switch and
// set the current ime to the proper one.
@@ -1937,7 +1621,13 @@
final String defaultImiId = mSettings.getSelectedInputMethod();
final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
- buildInputMethodListLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */);
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ currentUserId, AdditionalSubtypeMapRepository.get(currentUserId),
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(currentUserId, newSettings);
+ mSettings = newSettings;
+ postInputMethodSettingUpdatedLocked(
+ !imeSelectedOnBoot /* resetDefaultEnabledIme */);
updateFromSettingsLocked(true);
InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
getPackageManagerForUser(mContext, currentUserId),
@@ -2044,9 +1734,7 @@
&& (!connectionless
|| mBindingController.supportsConnectionlessStylusHandwriting());
}
- //TODO(b/197848765): This can be optimized by caching multi-user methodMaps/methodList.
- //TODO(b/210039666): use cache.
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final InputMethodInfo imi = settings.getMethodMap().get(
settings.getSelectedInputMethod());
return imi != null && imi.supportsStylusHandwriting()
@@ -2070,9 +1758,8 @@
private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId,
@DirectBootAwareness int directBootAwareness, int callingUid) {
final InputMethodSettings settings;
- if (userId == mSettings.getUserId()
- && directBootAwareness == DirectBootAwareness.AUTO) {
- settings = mSettings;
+ if (directBootAwareness == DirectBootAwareness.AUTO) {
+ settings = InputMethodSettingsRepository.get(userId);
} else {
final AdditionalSubtypeMap additionalSubtypeMap =
AdditionalSubtypeMapRepository.get(userId);
@@ -2096,7 +1783,7 @@
methodList = mSettings.getEnabledInputMethodList();
settings = mSettings;
} else {
- settings = queryMethodMapForUserLocked(userId);
+ settings = InputMethodSettingsRepository.get(userId);
methodList = settings.getEnabledInputMethodList();
}
// filter caller's access to input methods
@@ -2156,22 +1843,7 @@
@GuardedBy("ImfLock.class")
private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(String imiId,
boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId, int callingUid) {
- if (userId == mSettings.getUserId()) {
- final InputMethodInfo imi;
- String selectedMethodId = getSelectedMethodIdLocked();
- if (imiId == null && selectedMethodId != null) {
- imi = mSettings.getMethodMap().get(selectedMethodId);
- } else {
- imi = mSettings.getMethodMap().get(imiId);
- }
- if (imi == null || !canCallerAccessInputMethod(
- imi.getPackageName(), callingUid, userId, mSettings)) {
- return Collections.emptyList();
- }
- return mSettings.getEnabledInputMethodSubtypeList(
- imi, allowsImplicitlyEnabledSubtypes);
- }
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final InputMethodInfo imi = settings.getMethodMap().get(imiId);
if (imi == null) {
return Collections.emptyList();
@@ -4337,8 +4009,7 @@
return mSettings.getLastInputMethodSubtype();
}
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
- return settings.getLastInputMethodSubtype();
+ return InputMethodSettingsRepository.get(userId).getLastInputMethodSubtype();
}
}
@@ -4370,19 +4041,21 @@
final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
final boolean isCurrentUser = (mSettings.getUserId() == userId);
- final InputMethodSettings settings = isCurrentUser
- ? mSettings
- : queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
- DirectBootAwareness.AUTO);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final var newAdditionalSubtypeMap = settings.getNewAdditionalSubtypeMap(
imiId, toBeAdded, additionalSubtypeMap, mPackageManagerInternal, callingUid);
if (additionalSubtypeMap != newAdditionalSubtypeMap) {
AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
settings.getMethodMap());
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ userId, AdditionalSubtypeMapRepository.get(userId),
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, newSettings);
if (isCurrentUser) {
final long ident = Binder.clearCallingIdentity();
try {
- buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
+ mSettings = newSettings;
+ postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -4412,8 +4085,7 @@
try {
synchronized (ImfLock.class) {
final boolean currentUser = (mSettings.getUserId() == userId);
- final InputMethodSettings settings = currentUser
- ? mSettings : queryMethodMapForUserLocked(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
if (!settings.setEnabledInputMethodSubtypes(imeId, subtypeHashCodes)) {
return;
}
@@ -5310,7 +4982,7 @@
}
@GuardedBy("ImfLock.class")
- void buildInputMethodListLocked(boolean resetDefaultEnabledIme) {
+ void postInputMethodSettingUpdatedLocked(boolean resetDefaultEnabledIme) {
if (DEBUG) {
Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
+ " \n ------ caller=" + Debug.getCallers(10));
@@ -5322,10 +4994,6 @@
mMethodMapUpdateCount++;
mMyPackageMonitor.clearKnownImePackageNamesLocked();
- mSettings = queryInputMethodServicesInternal(mContext, mSettings.getUserId(),
- AdditionalSubtypeMapRepository.get(mSettings.getUserId()),
- DirectBootAwareness.AUTO);
-
// Construct the set of possible IME packages for onPackageChanged() to avoid false
// negatives when the package state remains to be the same but only the component state is
// changed.
@@ -5596,8 +5264,8 @@
return getCurrentInputMethodSubtypeLocked();
}
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
- return settings.getCurrentInputMethodSubtypeForNonCurrentUsers();
+ return InputMethodSettingsRepository.get(userId)
+ .getCurrentInputMethodSubtypeForNonCurrentUsers();
}
}
@@ -5659,27 +5327,11 @@
*/
@GuardedBy("ImfLock.class")
private InputMethodInfo queryDefaultInputMethodForUserIdLocked(@UserIdInt int userId) {
- final InputMethodSettings settings;
- if (userId == mSettings.getUserId()) {
- settings = mSettings;
- } else {
- final AdditionalSubtypeMap additionalSubtypeMap =
- AdditionalSubtypeMapRepository.get(userId);
- settings = queryInputMethodServicesInternal(mContext, userId,
- additionalSubtypeMap, DirectBootAwareness.AUTO);
- }
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
return settings.getMethodMap().get(settings.getSelectedInputMethod());
}
@GuardedBy("ImfLock.class")
- private InputMethodSettings queryMethodMapForUserLocked(@UserIdInt int userId) {
- final AdditionalSubtypeMap additionalSubtypeMap =
- AdditionalSubtypeMapRepository.get(userId);
- return queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
- DirectBootAwareness.AUTO);
- }
-
- @GuardedBy("ImfLock.class")
private boolean switchToInputMethodLocked(String imeId, @UserIdInt int userId) {
if (userId == mSettings.getUserId()) {
if (!mSettings.getMethodMap().containsKey(imeId)
@@ -5690,7 +5342,7 @@
setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID);
return true;
}
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
if (!settings.getMethodMap().containsKey(imeId)
|| !settings.getEnabledInputMethodList().contains(
settings.getMethodMap().get(imeId))) {
@@ -5830,7 +5482,7 @@
setInputMethodEnabledLocked(imeId, enabled);
return true;
}
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
if (!settings.getMethodMap().containsKey(imeId)) {
return false; // IME is not found.
}
@@ -6584,7 +6236,7 @@
previouslyEnabled = setInputMethodEnabledLocked(imeId, enabled);
}
} else {
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
if (enabled) {
if (!settings.getMethodMap().containsKey(imeId)) {
failedToEnableUnknownIme = true;
@@ -6718,10 +6370,8 @@
nextIme = mSettings.getSelectedInputMethod();
nextEnabledImes = mSettings.getEnabledInputMethodList();
} else {
- final AdditionalSubtypeMap additionalSubtypeMap =
- AdditionalSubtypeMapRepository.get(userId);
- final InputMethodSettings settings = queryInputMethodServicesInternal(
- mContext, userId, additionalSubtypeMap, DirectBootAwareness.AUTO);
+ final InputMethodSettings settings =
+ InputMethodSettingsRepository.get(userId);
nextEnabledImes = InputMethodInfoUtils.getDefaultEnabledImes(mContext,
settings.getMethodList());
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
new file mode 100644
index 0000000..60b9a4c
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.Handler;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+final class InputMethodSettingsRepository {
+ @GuardedBy("ImfLock.class")
+ @NonNull
+ private static final SparseArray<InputMethodSettings> sPerUserMap = new SparseArray<>();
+
+ /**
+ * Not intended to be instantiated.
+ */
+ private InputMethodSettingsRepository() {
+ }
+
+ @NonNull
+ @GuardedBy("ImfLock.class")
+ static InputMethodSettings get(@UserIdInt int userId) {
+ final InputMethodSettings obj = sPerUserMap.get(userId);
+ if (obj != null) {
+ return obj;
+ }
+ return InputMethodSettings.createEmptyMap(userId);
+ }
+
+ @GuardedBy("ImfLock.class")
+ static void put(@UserIdInt int userId, @NonNull InputMethodSettings obj) {
+ sPerUserMap.put(userId, obj);
+ }
+
+ static void initialize(@NonNull Handler handler, @NonNull Context context) {
+ final UserManagerInternal userManagerInternal =
+ LocalServices.getService(UserManagerInternal.class);
+ handler.post(() -> {
+ userManagerInternal.addUserLifecycleListener(
+ new UserManagerInternal.UserLifecycleListener() {
+ @Override
+ public void onUserRemoved(UserInfo user) {
+ final int userId = user.id;
+ handler.post(() -> {
+ synchronized (ImfLock.class) {
+ sPerUserMap.remove(userId);
+ }
+ });
+ }
+ });
+ synchronized (ImfLock.class) {
+ for (int userId : userManagerInternal.getUserIds()) {
+ final InputMethodSettings settings =
+ InputMethodManagerService.queryInputMethodServicesInternal(
+ context,
+ userId,
+ AdditionalSubtypeMapRepository.get(userId),
+ DirectBootAwareness.AUTO);
+ sPerUserMap.put(userId, settings);
+ }
+ }
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java b/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java
new file mode 100644
index 0000000..3023603
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.SystemClock;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
+
+import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicInteger;
+
+final class SoftInputShowHideHistory {
+ private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
+
+ private final Entry[] mEntries = new Entry[16];
+ private int mNextIndex = 0;
+
+ static final class Entry {
+ final int mSequenceNumber = sSequenceNumber.getAndIncrement();
+ @Nullable
+ final ClientState mClientState;
+ @WindowManager.LayoutParams.SoftInputModeFlags
+ final int mFocusedWindowSoftInputMode;
+ @SoftInputShowHideReason
+ final int mReason;
+ // The timing of handling showCurrentInputLocked() or hideCurrentInputLocked().
+ final long mTimestamp;
+ final long mWallTime;
+ final boolean mInFullscreenMode;
+ @NonNull
+ final String mFocusedWindowName;
+ @Nullable
+ final EditorInfo mEditorInfo;
+ @NonNull
+ final String mRequestWindowName;
+ @Nullable
+ final String mImeControlTargetName;
+ @Nullable
+ final String mImeTargetNameFromWm;
+ @Nullable
+ final String mImeSurfaceParentName;
+
+ Entry(ClientState client, EditorInfo editorInfo,
+ String focusedWindowName,
+ @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode,
+ @SoftInputShowHideReason int reason,
+ boolean inFullscreenMode, String requestWindowName,
+ @Nullable String imeControlTargetName, @Nullable String imeTargetName,
+ @Nullable String imeSurfaceParentName) {
+ mClientState = client;
+ mEditorInfo = editorInfo;
+ mFocusedWindowName = focusedWindowName;
+ mFocusedWindowSoftInputMode = softInputMode;
+ mReason = reason;
+ mTimestamp = SystemClock.uptimeMillis();
+ mWallTime = System.currentTimeMillis();
+ mInFullscreenMode = inFullscreenMode;
+ mRequestWindowName = requestWindowName;
+ mImeControlTargetName = imeControlTargetName;
+ mImeTargetNameFromWm = imeTargetName;
+ mImeSurfaceParentName = imeSurfaceParentName;
+ }
+ }
+
+ void addEntry(@NonNull Entry entry) {
+ final int index = mNextIndex;
+ mEntries[index] = entry;
+ mNextIndex = (mNextIndex + 1) % mEntries.length;
+ }
+
+ void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ final DateTimeFormatter formatter =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
+ .withZone(ZoneId.systemDefault());
+
+ for (int i = 0; i < mEntries.length; ++i) {
+ final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
+ if (entry == null) {
+ continue;
+ }
+ pw.print(prefix);
+ pw.println("SoftInputShowHide #" + entry.mSequenceNumber + ":");
+
+ pw.print(prefix);
+ pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
+ + " (timestamp=" + entry.mTimestamp + ")");
+
+ pw.print(prefix);
+ pw.print(" reason=" + InputMethodDebug.softInputDisplayReasonToString(
+ entry.mReason));
+ pw.println(" inFullscreenMode=" + entry.mInFullscreenMode);
+
+ pw.print(prefix);
+ pw.println(" requestClient=" + entry.mClientState);
+
+ pw.print(prefix);
+ pw.println(" focusedWindowName=" + entry.mFocusedWindowName);
+
+ pw.print(prefix);
+ pw.println(" requestWindowName=" + entry.mRequestWindowName);
+
+ pw.print(prefix);
+ pw.println(" imeControlTargetName=" + entry.mImeControlTargetName);
+
+ pw.print(prefix);
+ pw.println(" imeTargetNameFromWm=" + entry.mImeTargetNameFromWm);
+
+ pw.print(prefix);
+ pw.println(" imeSurfaceParentName=" + entry.mImeSurfaceParentName);
+
+ pw.print(prefix);
+ pw.print(" editorInfo:");
+ if (entry.mEditorInfo != null) {
+ pw.print(" inputType=" + entry.mEditorInfo.inputType);
+ pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions);
+ pw.println(" fieldId (viewId)=" + entry.mEditorInfo.fieldId);
+ } else {
+ pw.println(" null");
+ }
+
+ pw.print(prefix);
+ pw.println(" focusedWindowSoftInputMode=" + InputMethodDebug.softInputModeToString(
+ entry.mFocusedWindowSoftInputMode));
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/StartInputHistory.java b/services/core/java/com/android/server/inputmethod/StartInputHistory.java
new file mode 100644
index 0000000..3a39434
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/StartInputHistory.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.internal.inputmethod.StartInputReason;
+
+import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+
+/**
+ * A ring buffer to store the history of {@link StartInputInfo}.
+ */
+final class StartInputHistory {
+ /**
+ * Entry size for non low-RAM devices.
+ *
+ * <p>TODO: Consider to follow what other system services have been doing to manage
+ * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
+ */
+ private static final int ENTRY_SIZE_FOR_HIGH_RAM_DEVICE = 32;
+
+ /**
+ * Entry size for low-RAM devices.
+ *
+ * <p>TODO: Consider to follow what other system services have been doing to manage
+ * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
+ */
+ private static final int ENTRY_SIZE_FOR_LOW_RAM_DEVICE = 5;
+
+ private static int getEntrySize() {
+ if (ActivityManager.isLowRamDeviceStatic()) {
+ return ENTRY_SIZE_FOR_LOW_RAM_DEVICE;
+ } else {
+ return ENTRY_SIZE_FOR_HIGH_RAM_DEVICE;
+ }
+ }
+
+ /**
+ * Backing store for the ring buffer.
+ */
+ private final Entry[] mEntries = new Entry[getEntrySize()];
+
+ /**
+ * An index of {@link #mEntries}, to which next
+ * {@link #addEntry(StartInputInfo)} should
+ * write.
+ */
+ private int mNextIndex = 0;
+
+ /**
+ * Recyclable entry to store the information in {@link StartInputInfo}.
+ */
+ private static final class Entry {
+ int mSequenceNumber;
+ long mTimestamp;
+ long mWallTime;
+ @UserIdInt
+ int mImeUserId;
+ @NonNull
+ String mImeTokenString;
+ int mImeDisplayId;
+ @NonNull
+ String mImeId;
+ @StartInputReason
+ int mStartInputReason;
+ boolean mRestarting;
+ @UserIdInt
+ int mTargetUserId;
+ int mTargetDisplayId;
+ @NonNull
+ String mTargetWindowString;
+ @NonNull
+ EditorInfo mEditorInfo;
+ @WindowManager.LayoutParams.SoftInputModeFlags
+ int mTargetWindowSoftInputMode;
+ int mClientBindSequenceNumber;
+
+ Entry(@NonNull StartInputInfo original) {
+ set(original);
+ }
+
+ void set(@NonNull StartInputInfo original) {
+ mSequenceNumber = original.mSequenceNumber;
+ mTimestamp = original.mTimestamp;
+ mWallTime = original.mWallTime;
+ mImeUserId = original.mImeUserId;
+ // Intentionally convert to String so as not to keep a strong reference to a Binder
+ // object.
+ mImeTokenString = String.valueOf(original.mImeToken);
+ mImeDisplayId = original.mImeDisplayId;
+ mImeId = original.mImeId;
+ mStartInputReason = original.mStartInputReason;
+ mRestarting = original.mRestarting;
+ mTargetUserId = original.mTargetUserId;
+ mTargetDisplayId = original.mTargetDisplayId;
+ // Intentionally convert to String so as not to keep a strong reference to a Binder
+ // object.
+ mTargetWindowString = String.valueOf(original.mTargetWindow);
+ mEditorInfo = original.mEditorInfo;
+ mTargetWindowSoftInputMode = original.mTargetWindowSoftInputMode;
+ mClientBindSequenceNumber = original.mClientBindSequenceNumber;
+ }
+ }
+
+ /**
+ * Add a new entry and discard the oldest entry as needed.
+ *
+ * @param info {@link StartInputInfo} to be added.
+ */
+ void addEntry(@NonNull StartInputInfo info) {
+ final int index = mNextIndex;
+ if (mEntries[index] == null) {
+ mEntries[index] = new Entry(info);
+ } else {
+ mEntries[index].set(info);
+ }
+ mNextIndex = (mNextIndex + 1) % mEntries.length;
+ }
+
+ void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ final DateTimeFormatter formatter =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
+ .withZone(ZoneId.systemDefault());
+
+ for (int i = 0; i < mEntries.length; ++i) {
+ final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
+ if (entry == null) {
+ continue;
+ }
+ pw.print(prefix);
+ pw.println("StartInput #" + entry.mSequenceNumber + ":");
+
+ pw.print(prefix);
+ pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
+ + " (timestamp=" + entry.mTimestamp + ")"
+ + " reason="
+ + InputMethodDebug.startInputReasonToString(entry.mStartInputReason)
+ + " restarting=" + entry.mRestarting);
+
+ pw.print(prefix);
+ pw.print(" imeToken=" + entry.mImeTokenString + " [" + entry.mImeId + "]");
+ pw.print(" imeUserId=" + entry.mImeUserId);
+ pw.println(" imeDisplayId=" + entry.mImeDisplayId);
+
+ pw.print(prefix);
+ pw.println(" targetWin=" + entry.mTargetWindowString
+ + " [" + entry.mEditorInfo.packageName + "]"
+ + " targetUserId=" + entry.mTargetUserId
+ + " targetDisplayId=" + entry.mTargetDisplayId
+ + " clientBindSeq=" + entry.mClientBindSequenceNumber);
+
+ pw.print(prefix);
+ pw.println(" softInputMode=" + InputMethodDebug.softInputModeToString(
+ entry.mTargetWindowSoftInputMode));
+
+ pw.print(prefix);
+ pw.println(" inputType=0x" + Integer.toHexString(entry.mEditorInfo.inputType)
+ + " imeOptions=0x" + Integer.toHexString(entry.mEditorInfo.imeOptions)
+ + " fieldId=0x" + Integer.toHexString(entry.mEditorInfo.fieldId)
+ + " fieldName=" + entry.mEditorInfo.fieldName
+ + " actionId=" + entry.mEditorInfo.actionId
+ + " actionLabel=" + entry.mEditorInfo.actionLabel);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/StartInputInfo.java b/services/core/java/com/android/server/inputmethod/StartInputInfo.java
new file mode 100644
index 0000000..1cff737
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/StartInputInfo.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.inputmethodservice.InputMethodService;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+
+import com.android.internal.inputmethod.IInputMethod;
+import com.android.internal.inputmethod.StartInputReason;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Internal state snapshot when
+ * {@link IInputMethod#startInput(IInputMethod.StartInputParams)} is about to be called.
+ *
+ * <p>Calling that IPC endpoint basically means that
+ * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called
+ * back in the current IME process shortly, which will also affect what the current IME starts
+ * receiving from {@link InputMethodService#getCurrentInputConnection()}. In other words, this
+ * snapshot will be taken every time when {@link InputMethodManagerService} is initiating a new
+ * logical input session between the client application and the current IME.</p>
+ *
+ * <p>Be careful to not keep strong references to this object forever, which can prevent
+ * {@link StartInputInfo#mImeToken} and {@link StartInputInfo#mTargetWindow} from being GC-ed.
+ * </p>
+ */
+final class StartInputInfo {
+ private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
+
+ final int mSequenceNumber;
+ final long mTimestamp;
+ final long mWallTime;
+ @UserIdInt
+ final int mImeUserId;
+ @NonNull
+ final IBinder mImeToken;
+ final int mImeDisplayId;
+ @NonNull
+ final String mImeId;
+ @StartInputReason
+ final int mStartInputReason;
+ final boolean mRestarting;
+ @UserIdInt
+ final int mTargetUserId;
+ final int mTargetDisplayId;
+ @Nullable
+ final IBinder mTargetWindow;
+ @NonNull
+ final EditorInfo mEditorInfo;
+ @WindowManager.LayoutParams.SoftInputModeFlags
+ final int mTargetWindowSoftInputMode;
+ final int mClientBindSequenceNumber;
+
+ StartInputInfo(@UserIdInt int imeUserId, @NonNull IBinder imeToken, int imeDisplayId,
+ @NonNull String imeId, @StartInputReason int startInputReason, boolean restarting,
+ @UserIdInt int targetUserId, int targetDisplayId, @Nullable IBinder targetWindow,
+ @NonNull EditorInfo editorInfo,
+ @WindowManager.LayoutParams.SoftInputModeFlags int targetWindowSoftInputMode,
+ int clientBindSequenceNumber) {
+ mSequenceNumber = sSequenceNumber.getAndIncrement();
+ mTimestamp = SystemClock.uptimeMillis();
+ mWallTime = System.currentTimeMillis();
+ mImeUserId = imeUserId;
+ mImeToken = imeToken;
+ mImeDisplayId = imeDisplayId;
+ mImeId = imeId;
+ mStartInputReason = startInputReason;
+ mRestarting = restarting;
+ mTargetUserId = targetUserId;
+ mTargetDisplayId = targetDisplayId;
+ mTargetWindow = targetWindow;
+ mEditorInfo = editorInfo;
+ mTargetWindowSoftInputMode = targetWindowSoftInputMode;
+ mClientBindSequenceNumber = clientBindSequenceNumber;
+ }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b98424c..c1a4571e 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -338,6 +338,7 @@
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.TriPredicate;
@@ -1825,6 +1826,12 @@
}
}
+ protected void logSensitiveAdjustmentReceived(boolean hasPosted,
+ boolean hasSensitiveContent, int lifespanMs) {
+ FrameworkStatsLog.write(FrameworkStatsLog.SENSITIVE_NOTIFICATION_REDACTION, hasPosted,
+ hasSensitiveContent, lifespanMs);
+ }
+
@GuardedBy("mNotificationLock")
void clearSoundLocked() {
mSoundNotificationKey = null;
@@ -6384,7 +6391,7 @@
if (Objects.equals(adjustment.getKey(), r.getKey())
&& Objects.equals(adjustment.getUser(), r.getUserId())
&& mAssistants.isSameUser(token, r.getUserId())) {
- applyAdjustment(r, adjustment);
+ applyAdjustmentLocked(r, adjustment, false);
r.applyAdjustments();
// importance is checked at the beginning of the
// PostNotificationRunnable, before the signal extractors are run, so
@@ -6394,7 +6401,7 @@
}
}
if (!foundEnqueued) {
- applyAdjustmentFromAssistant(token, adjustment);
+ applyAdjustmentsFromAssistant(token, List.of(adjustment));
}
}
} finally {
@@ -6422,7 +6429,7 @@
for (Adjustment adjustment : adjustments) {
NotificationRecord r = mNotificationsByKey.get(adjustment.getKey());
if (r != null && mAssistants.isSameUser(token, r.getUserId())) {
- applyAdjustment(r, adjustment);
+ applyAdjustmentLocked(r, adjustment, true);
// If the assistant has blocked the notification, cancel it
// This will trigger a sort, so we don't have to explicitly ask for
// one here.
@@ -6706,7 +6713,9 @@
}
}
- private void applyAdjustment(NotificationRecord r, Adjustment adjustment) {
+ @GuardedBy("mNotificationLock")
+ private void applyAdjustmentLocked(NotificationRecord r, Adjustment adjustment,
+ boolean isPosted) {
if (r == null) {
return;
}
@@ -6723,6 +6732,11 @@
adjustments.remove(removeKey);
}
r.addAdjustment(adjustment);
+ if (adjustment.getSignals().containsKey(Adjustment.KEY_SENSITIVE_CONTENT)) {
+ logSensitiveAdjustmentReceived(isPosted,
+ adjustment.getSignals().getBoolean(Adjustment.KEY_SENSITIVE_CONTENT),
+ r.getLifespanMs(System.currentTimeMillis()));
+ }
}
}
@@ -8210,7 +8224,7 @@
try {
return mTelecomManager.isInManagedCall()
|| mTelecomManager.isInSelfManagedCall(pkg,
- /* hasCrossUserAccess */ true);
+ UserHandle.ALL);
} catch (IllegalStateException ise) {
// Telecom is not ready (this is likely early boot), so there are no calls.
return false;
@@ -12054,10 +12068,17 @@
@Override
public void onServiceAdded(ManagedServiceInfo info) {
if (lifetimeExtensionRefactor()) {
- // We explicitly check the status bar permission for the uid in the info object.
- // We can't use the calling uid here because it's probably always system server.
- // Note that this will also be true for the shell.
- info.isSystemUi = getContext().checkPermission(
+ // Generally, only System or System UI should have the permissions to call
+ // registerSystemService.
+ // isCallerSystemOrPhone tells us whether the caller is System. We negate this,
+ // to eliminate cases where the service was added by the system. This leaves
+ // services registered by system server.
+ // To identify system UI, we explicitly check the status bar permission for the
+ // uid in the info object.
+ // We can't use the calling uid here because it belongs to system server.
+ // Note that this will also return true for the shell, but we deem this
+ // acceptable, for the purposes of testing.
+ info.isSystemUi = !isCallerSystemOrPhone() && getContext().checkPermission(
android.Manifest.permission.STATUS_BAR_SERVICE, -1, info.uid)
== PERMISSION_GRANTED;
}
diff --git a/core/java/android/app/ondeviceintelligence/Content.aidl b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java
similarity index 72%
rename from core/java/android/app/ondeviceintelligence/Content.aidl
rename to services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java
index 40f0ef9..81f11b5 100644
--- a/core/java/android/app/ondeviceintelligence/Content.aidl
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java
@@ -1,5 +1,5 @@
-/**
- * Copyright (c) 2024, The Android Open Source Project
+/*
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package android.app.ondeviceintelligence;
+package com.android.server.ondeviceintelligence;
-/**
- * @hide
- */
-parcelable Content;
+public interface OnDeviceIntelligenceManagerInternal {
+ String getRemoteServicePackageName();
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
index 71800ef..28682e3 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
@@ -16,12 +16,11 @@
package com.android.server.ondeviceintelligence;
-import static android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException.PROCESSING_UPDATE_STATUS_CONNECTION_FAILED;
-
import android.Manifest;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.app.AppGlobals;
-import android.app.ondeviceintelligence.Content;
import android.app.ondeviceintelligence.DownloadCallback;
import android.app.ondeviceintelligence.Feature;
import android.app.ondeviceintelligence.IDownloadCallback;
@@ -33,25 +32,32 @@
import android.app.ondeviceintelligence.IResponseCallback;
import android.app.ondeviceintelligence.IStreamingResponseCallback;
import android.app.ondeviceintelligence.ITokenInfoCallback;
-import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceException;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.os.Binder;
+import android.content.res.Resources;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
import android.os.ICancellationSignal;
+import android.os.Looper;
+import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.service.ondeviceintelligence.IOnDeviceIntelligenceService;
import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService;
+import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback;
import android.service.ondeviceintelligence.IRemoteProcessingService;
import android.service.ondeviceintelligence.IRemoteStorageService;
-import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback;
import android.service.ondeviceintelligence.OnDeviceIntelligenceService;
import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
import android.text.TextUtils;
@@ -62,8 +68,10 @@
import com.android.internal.infra.AndroidFuture;
import com.android.internal.infra.ServiceConnector;
import com.android.internal.os.BackgroundThread;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
+import java.io.FileDescriptor;
import java.util.Objects;
import java.util.Set;
@@ -84,6 +92,9 @@
private static final String TAG = OnDeviceIntelligenceManagerService.class.getSimpleName();
private static final String KEY_SERVICE_ENABLED = "service_enabled";
+ /** Handler message to {@link #resetTemporaryServices()} */
+ private static final int MSG_RESET_TEMPORARY_SERVICE = 0;
+
/** Default value in absence of {@link DeviceConfig} override. */
private static final boolean DEFAULT_SERVICE_ENABLED = true;
private static final String NAMESPACE_ON_DEVICE_INTELLIGENCE = "ondeviceintelligence";
@@ -96,19 +107,30 @@
private RemoteOnDeviceIntelligenceService mRemoteOnDeviceIntelligenceService;
volatile boolean mIsServiceEnabled;
+ @GuardedBy("mLock")
+ private String[] mTemporaryServiceNames;
+
+ /**
+ * Handler used to reset the temporary service names.
+ */
+ @GuardedBy("mLock")
+ private Handler mTemporaryHandler;
+
public OnDeviceIntelligenceManagerService(Context context) {
super(context);
mContext = context;
+ mTemporaryServiceNames = new String[0];
}
@Override
public void onStart() {
publishBinderService(
- Context.ON_DEVICE_INTELLIGENCE_SERVICE, new OnDeviceIntelligenceManagerInternal(),
+ Context.ON_DEVICE_INTELLIGENCE_SERVICE, getOnDeviceIntelligenceManagerService(),
/* allowIsolated = */true);
+ LocalServices.addService(OnDeviceIntelligenceManagerInternal.class,
+ OnDeviceIntelligenceManagerService.this::getRemoteConfiguredPackageName);
}
-
@Override
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
@@ -133,195 +155,211 @@
KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED);
}
- private final class OnDeviceIntelligenceManagerInternal extends
- IOnDeviceIntelligenceManager.Stub {
- @Override
- public void getVersion(RemoteCallback remoteCallback) throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getVersion");
- Objects.requireNonNull(remoteCallback);
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- remoteCallback.sendResult(null);
- return;
+ private IBinder getOnDeviceIntelligenceManagerService() {
+ return new IOnDeviceIntelligenceManager.Stub() {
+ @Override
+ public String getRemoteServicePackageName() {
+ return OnDeviceIntelligenceManagerService.this.getRemoteConfiguredPackageName();
}
- ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.post(
- service -> service.getVersion(remoteCallback));
- }
- @Override
- public void getFeature(int id, IFeatureCallback featureCallback) throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
- Objects.requireNonNull(featureCallback);
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- featureCallback.onFailure(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- new PersistableBundle());
- return;
+ @Override
+ public void getVersion(RemoteCallback remoteCallback) throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getVersion");
+ Objects.requireNonNull(remoteCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ remoteCallback.sendResult(null);
+ return;
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.run(
+ service -> service.getVersion(remoteCallback));
}
- ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.post(
- service -> service.getFeature(Binder.getCallingUid(), id, featureCallback));
- }
- @Override
- public void listFeatures(IListFeaturesCallback listFeaturesCallback)
- throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
- Objects.requireNonNull(listFeaturesCallback);
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- listFeaturesCallback.onFailure(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- new PersistableBundle());
- return;
+ @Override
+ public void getFeature(int id, IFeatureCallback featureCallback)
+ throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
+ Objects.requireNonNull(featureCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ featureCallback.onFailure(
+ OnDeviceIntelligenceException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ PersistableBundle.EMPTY);
+ return;
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.run(
+ service -> service.getFeature(Binder.getCallingUid(), id, featureCallback));
}
- ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.post(
- service -> service.listFeatures(Binder.getCallingUid(), listFeaturesCallback));
- }
- @Override
- public void getFeatureDetails(Feature feature,
- IFeatureDetailsCallback featureDetailsCallback)
- throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatureStatus");
- Objects.requireNonNull(feature);
- Objects.requireNonNull(featureDetailsCallback);
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- featureDetailsCallback.onFailure(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- new PersistableBundle());
- return;
+ @Override
+ public void listFeatures(IListFeaturesCallback listFeaturesCallback)
+ throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
+ Objects.requireNonNull(listFeaturesCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ listFeaturesCallback.onFailure(
+ OnDeviceIntelligenceException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ PersistableBundle.EMPTY);
+ return;
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.run(
+ service -> service.listFeatures(Binder.getCallingUid(),
+ listFeaturesCallback));
}
- ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.post(
- service -> service.getFeatureDetails(Binder.getCallingUid(), feature,
- featureDetailsCallback));
- }
- @Override
- public void requestFeatureDownload(Feature feature, ICancellationSignal cancellationSignal,
- IDownloadCallback downloadCallback) throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal requestFeatureDownload");
- Objects.requireNonNull(feature);
- Objects.requireNonNull(downloadCallback);
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- downloadCallback.onDownloadFailed(
- DownloadCallback.DOWNLOAD_FAILURE_STATUS_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- new PersistableBundle());
+ @Override
+ public void getFeatureDetails(Feature feature,
+ IFeatureDetailsCallback featureDetailsCallback)
+ throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatureStatus");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(featureDetailsCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ featureDetailsCallback.onFailure(
+ OnDeviceIntelligenceException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ PersistableBundle.EMPTY);
+ return;
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.run(
+ service -> service.getFeatureDetails(Binder.getCallingUid(), feature,
+ featureDetailsCallback));
}
- ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.post(
- service -> service.requestFeatureDownload(Binder.getCallingUid(), feature,
- cancellationSignal,
- downloadCallback));
- }
-
- @Override
- public void requestTokenInfo(Feature feature,
- Content request, ICancellationSignal cancellationSignal,
- ITokenInfoCallback tokenInfoCallback) throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal prepareFeatureProcessing");
- Objects.requireNonNull(feature);
- Objects.requireNonNull(request);
- Objects.requireNonNull(tokenInfoCallback);
-
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- tokenInfoCallback.onFailure(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- new PersistableBundle());
+ @Override
+ public void requestFeatureDownload(Feature feature,
+ ICancellationSignal cancellationSignal,
+ IDownloadCallback downloadCallback) throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal requestFeatureDownload");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(downloadCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ downloadCallback.onDownloadFailed(
+ DownloadCallback.DOWNLOAD_FAILURE_STATUS_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ PersistableBundle.EMPTY);
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.run(
+ service -> service.requestFeatureDownload(Binder.getCallingUid(), feature,
+ cancellationSignal,
+ downloadCallback));
}
- ensureRemoteInferenceServiceInitialized();
- mRemoteInferenceService.post(
- service -> service.requestTokenInfo(Binder.getCallingUid(), feature, request,
- cancellationSignal,
- tokenInfoCallback));
- }
- @Override
- public void processRequest(Feature feature,
- Content request,
- int requestType,
- ICancellationSignal cancellationSignal,
- IProcessingSignal processingSignal,
- IResponseCallback responseCallback)
- throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequest");
- Objects.requireNonNull(feature);
- Objects.requireNonNull(responseCallback);
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- responseCallback.onFailure(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException.PROCESSING_ERROR_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- new PersistableBundle());
- }
- ensureRemoteInferenceServiceInitialized();
- mRemoteInferenceService.post(
- service -> service.processRequest(Binder.getCallingUid(), feature, request,
- requestType,
- cancellationSignal, processingSignal,
- responseCallback));
- }
- @Override
- public void processRequestStreaming(Feature feature,
- Content request,
- int requestType,
- ICancellationSignal cancellationSignal,
- IProcessingSignal processingSignal,
- IStreamingResponseCallback streamingCallback) throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequestStreaming");
- Objects.requireNonNull(feature);
- Objects.requireNonNull(streamingCallback);
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- streamingCallback.onFailure(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException.PROCESSING_ERROR_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- new PersistableBundle());
+ @Override
+ public void requestTokenInfo(Feature feature,
+ Bundle request, ICancellationSignal cancellationSignal,
+ ITokenInfoCallback tokenInfoCallback) throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal prepareFeatureProcessing");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(tokenInfoCallback);
+
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ tokenInfoCallback.onFailure(
+ OnDeviceIntelligenceException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ PersistableBundle.EMPTY);
+ }
+ ensureRemoteInferenceServiceInitialized();
+ mRemoteInferenceService.run(
+ service -> service.requestTokenInfo(Binder.getCallingUid(), feature,
+ request,
+ cancellationSignal,
+ tokenInfoCallback));
}
- ensureRemoteInferenceServiceInitialized();
- mRemoteInferenceService.post(
- service -> service.processRequestStreaming(Binder.getCallingUid(), feature,
- request, requestType,
- cancellationSignal, processingSignal,
- streamingCallback));
- }
+
+ @Override
+ public void processRequest(Feature feature,
+ Bundle request,
+ int requestType,
+ ICancellationSignal cancellationSignal,
+ IProcessingSignal processingSignal,
+ IResponseCallback responseCallback)
+ throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequest");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(responseCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ responseCallback.onFailure(
+ OnDeviceIntelligenceException.PROCESSING_ERROR_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ PersistableBundle.EMPTY);
+ }
+ ensureRemoteInferenceServiceInitialized();
+ mRemoteInferenceService.run(
+ service -> service.processRequest(Binder.getCallingUid(), feature, request,
+ requestType,
+ cancellationSignal, processingSignal,
+ responseCallback));
+ }
+
+ @Override
+ public void processRequestStreaming(Feature feature,
+ Bundle request,
+ int requestType,
+ ICancellationSignal cancellationSignal,
+ IProcessingSignal processingSignal,
+ IStreamingResponseCallback streamingCallback) throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequestStreaming");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(streamingCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ streamingCallback.onFailure(
+ OnDeviceIntelligenceException.PROCESSING_ERROR_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ PersistableBundle.EMPTY);
+ }
+ ensureRemoteInferenceServiceInitialized();
+ mRemoteInferenceService.run(
+ service -> service.processRequestStreaming(Binder.getCallingUid(), feature,
+ request, requestType,
+ cancellationSignal, processingSignal,
+ streamingCallback));
+ }
+
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
+ new OnDeviceIntelligenceShellCommand(OnDeviceIntelligenceManagerService.this).exec(
+ this, in, out, err, args, callback, resultReceiver);
+ }
+ };
}
private void ensureRemoteIntelligenceServiceInitialized() throws RemoteException {
synchronized (mLock) {
if (mRemoteOnDeviceIntelligenceService == null) {
- String serviceName = mContext.getResources().getString(
- R.string.config_defaultOnDeviceIntelligenceService);
+ String serviceName = getServiceNames()[0];
validateService(serviceName, false);
mRemoteOnDeviceIntelligenceService = new RemoteOnDeviceIntelligenceService(mContext,
ComponentName.unflattenFromString(serviceName),
@@ -352,13 +390,13 @@
IProcessingUpdateStatusCallback callback) {
try {
ensureRemoteInferenceServiceInitialized();
- mRemoteInferenceService.post(
+ mRemoteInferenceService.run(
service -> service.updateProcessingState(
processingState, callback));
} catch (RemoteException unused) {
try {
callback.onFailure(
- PROCESSING_UPDATE_STATUS_CONNECTION_FAILED,
+ OnDeviceIntelligenceException.PROCESSING_UPDATE_STATUS_CONNECTION_FAILED,
"Received failure invoking the remote processing service.");
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to send failure status.", ex);
@@ -371,8 +409,7 @@
private void ensureRemoteInferenceServiceInitialized() throws RemoteException {
synchronized (mLock) {
if (mRemoteInferenceService == null) {
- String serviceName = mContext.getResources().getString(
- R.string.config_defaultOnDeviceSandboxedInferenceService);
+ String serviceName = getServiceNames()[1];
validateService(serviceName, true);
mRemoteInferenceService = new RemoteOnDeviceSandboxedInferenceService(mContext,
ComponentName.unflattenFromString(serviceName),
@@ -384,7 +421,7 @@
@NonNull IOnDeviceSandboxedInferenceService service) {
try {
ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.post(
+ mRemoteOnDeviceIntelligenceService.run(
intelligenceService -> intelligenceService.notifyInferenceServiceConnected());
service.registerRemoteStorageService(
getIRemoteStorageService());
@@ -404,7 +441,7 @@
public void getReadOnlyFileDescriptor(
String filePath,
AndroidFuture<ParcelFileDescriptor> future) {
- mRemoteOnDeviceIntelligenceService.post(
+ mRemoteOnDeviceIntelligenceService.run(
service -> service.getReadOnlyFileDescriptor(
filePath, future));
}
@@ -413,7 +450,7 @@
public void getReadOnlyFeatureFileDescriptorMap(
Feature feature,
RemoteCallback remoteCallback) {
- mRemoteOnDeviceIntelligenceService.post(
+ mRemoteOnDeviceIntelligenceService.run(
service -> service.getReadOnlyFeatureFileDescriptorMap(
feature, remoteCallback));
}
@@ -469,4 +506,92 @@
return (serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
&& (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0;
}
+
+ @Nullable
+ public String getRemoteConfiguredPackageName() {
+ try {
+ String[] serviceNames = getServiceNames();
+ ComponentName componentName = ComponentName.unflattenFromString(serviceNames[1]);
+ if (componentName != null) {
+ return componentName.getPackageName();
+ }
+ } catch (Resources.NotFoundException e) {
+ Slog.e(TAG, "Could not find resource", e);
+ }
+
+ return null;
+ }
+
+
+ protected String[] getServiceNames() throws Resources.NotFoundException {
+ // TODO 329240495 : Consider a small class with explicit field names for the two services
+ synchronized (mLock) {
+ if (mTemporaryServiceNames != null && mTemporaryServiceNames.length == 2) {
+ return mTemporaryServiceNames;
+ }
+ }
+ return new String[]{mContext.getResources().getString(
+ R.string.config_defaultOnDeviceIntelligenceService),
+ mContext.getResources().getString(
+ R.string.config_defaultOnDeviceSandboxedInferenceService)};
+ }
+
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void setTemporaryServices(@NonNull String[] componentNames, int durationMs) {
+ Objects.requireNonNull(componentNames);
+ enforceShellOnly(Binder.getCallingUid(), "setTemporaryServices");
+ mContext.enforceCallingPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ synchronized (mLock) {
+ mTemporaryServiceNames = componentNames;
+
+ if (mTemporaryHandler == null) {
+ mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_RESET_TEMPORARY_SERVICE) {
+ synchronized (mLock) {
+ resetTemporaryServices();
+ }
+ } else {
+ Slog.wtf(TAG, "invalid handler msg: " + msg);
+ }
+ }
+ };
+ } else {
+ mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
+ }
+
+ if (durationMs != -1) {
+ mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, durationMs);
+ }
+ }
+ }
+
+ public void resetTemporaryServices() {
+ synchronized (mLock) {
+ if (mTemporaryHandler != null) {
+ mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
+ mTemporaryHandler = null;
+ }
+
+ mRemoteInferenceService = null;
+ mRemoteOnDeviceIntelligenceService = null;
+ mTemporaryServiceNames = new String[0];
+ }
+ }
+
+ /**
+ * Throws if the caller is not of a shell (or root) UID.
+ *
+ * @param callingUid pass Binder.callingUid().
+ */
+ public static void enforceShellOnly(int callingUid, String message) {
+ if (callingUid == android.os.Process.SHELL_UID
+ || callingUid == android.os.Process.ROOT_UID) {
+ return; // okay
+ }
+
+ throw new SecurityException(message + ": Only shell user can call it");
+ }
}
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
new file mode 100644
index 0000000..a76d8a3
--- /dev/null
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.ondeviceintelligence;
+
+import android.annotation.NonNull;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+final class OnDeviceIntelligenceShellCommand extends ShellCommand {
+ private static final String TAG = OnDeviceIntelligenceShellCommand.class.getSimpleName();
+
+ @NonNull
+ private final OnDeviceIntelligenceManagerService mService;
+
+ OnDeviceIntelligenceShellCommand(@NonNull OnDeviceIntelligenceManagerService service) {
+ mService = service;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+
+ switch (cmd) {
+ case "set-temporary-services":
+ return setTemporaryServices();
+ case "get-services":
+ return getConfiguredServices();
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ }
+
+ @Override
+ public void onHelp() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("OnDeviceIntelligenceShellCommand commands: ");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println();
+ pw.println(
+ " set-temporary-services [IntelligenceServiceComponentName] "
+ + "[InferenceServiceComponentName] [DURATION]");
+ pw.println(" Temporarily (for DURATION ms) changes the service implementations.");
+ pw.println(" To reset, call without any arguments.");
+
+ pw.println(" get-services To get the names of services that are currently being used.");
+ }
+
+ private int setTemporaryServices() {
+ final PrintWriter out = getOutPrintWriter();
+ final String intelligenceServiceName = getNextArg();
+ final String inferenceServiceName = getNextArg();
+ if (getRemainingArgsCount() == 0 && intelligenceServiceName == null
+ && inferenceServiceName == null) {
+ mService.resetTemporaryServices();
+ out.println("OnDeviceIntelligenceManagerService temporary reset. ");
+ return 0;
+ }
+
+ Objects.requireNonNull(intelligenceServiceName);
+ Objects.requireNonNull(inferenceServiceName);
+ final int duration = Integer.parseInt(getNextArgRequired());
+ mService.setTemporaryServices(
+ new String[]{intelligenceServiceName, inferenceServiceName}, duration);
+ out.println("OnDeviceIntelligenceService temporarily set to " + intelligenceServiceName
+ + " \n and \n OnDeviceTrustedInferenceService set to " + inferenceServiceName
+ + " for " + duration + "ms");
+ return 0;
+ }
+
+ private int getConfiguredServices() {
+ final PrintWriter out = getOutPrintWriter();
+ String[] services = mService.getServiceNames();
+ out.println("OnDeviceIntelligenceService set to : " + services[0]
+ + " \n and \n OnDeviceTrustedInferenceService set to : " + services[1]);
+ return 0;
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/permission/OWNERS b/services/core/java/com/android/server/permission/OWNERS
new file mode 100644
index 0000000..fb6099c
--- /dev/null
+++ b/services/core/java/com/android/server/permission/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 137825
+
+include platform/frameworks/base:/core/java/android/permission/OWNERS
diff --git a/services/core/java/com/android/server/permission/PermissionManagerLocal.java b/services/core/java/com/android/server/permission/PermissionManagerLocal.java
new file mode 100644
index 0000000..7251e6e
--- /dev/null
+++ b/services/core/java/com/android/server/permission/PermissionManagerLocal.java
@@ -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.server.permission;
+
+import android.annotation.TestApi;
+import com.android.internal.annotations.Keep;
+
+/**
+ * In-process API for server side permission related infrastructure.
+ *
+ * @hide
+ */
+@Keep
+@TestApi
+public interface PermissionManagerLocal {
+
+ /**
+ * Get whether signature permission allowlist is enforced even on debuggable builds.
+ *
+ * @return whether the signature permission allowlist is force enforced
+ */
+ @TestApi
+ boolean isSignaturePermissionAllowlistForceEnforced();
+
+ /**
+ * Set whether signature permission allowlist is enforced even on debuggable builds.
+ *
+ * @param forceEnforced whether the signature permission allowlist is force enforced
+ */
+ @TestApi
+ void setSignaturePermissionAllowlistForceEnforced(boolean forceEnforced);
+}
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index c7ebb3c..c6bb99e 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -2116,6 +2116,18 @@
@RequiresPermission(READ_FRAME_BUFFER)
@Override
+ public void saveViewCaptureData() {
+ int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER);
+ if (PERMISSION_GRANTED == status) {
+ forEachViewCaptureWindow(this::dumpViewCaptureDataToWmTrace);
+ } else {
+ Log.w(TAG, "caller lacks permissions to save view capture data");
+ }
+ }
+
+
+ @RequiresPermission(READ_FRAME_BUFFER)
+ @Override
public void registerDumpCallback(@NonNull IDumpCallback cb) {
int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER);
if (PERMISSION_GRANTED == status) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerLocal.java b/services/core/java/com/android/server/pm/PackageManagerLocal.java
index 6266ef3..4ed6e80 100644
--- a/services/core/java/com/android/server/pm/PackageManagerLocal.java
+++ b/services/core/java/com/android/server/pm/PackageManagerLocal.java
@@ -20,6 +20,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.content.pm.SigningDetails;
import android.os.Binder;
import android.os.UserHandle;
@@ -124,6 +126,48 @@
FilteredSnapshot withFilteredSnapshot(int callingUid, @NonNull UserHandle user);
/**
+ * Add a pair of signing details so that packages signed with {@code oldSigningDetails} will
+ * behave as if they are signed by the {@code newSigningDetails}.
+ * <p>
+ * This is only available on {@link android.os.Build#isDebuggable debuggable} builds.
+ *
+ * @param oldSigningDetails the original signing detail of the package
+ * @param newSigningDetails the new signing detail that will replace the original one
+ * @throws SecurityException if the build is not debuggable
+ *
+ * @hide
+ */
+ @TestApi
+ void addOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails,
+ @NonNull SigningDetails newSigningDetails);
+
+ /**
+ * Remove a pair of signing details previously added via {@link #addOverrideSigningDetails} by
+ * the old signing details.
+ * <p>
+ * This is only available on {@link android.os.Build#isDebuggable debuggable} builds.
+ *
+ * @param oldSigningDetails the original signing detail of the package
+ * @throws SecurityException if the build is not debuggable
+ *
+ * @hide
+ */
+ @TestApi
+ void removeOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails);
+
+ /**
+ * Clear all pairs of signing details previously added via {@link #addOverrideSigningDetails}.
+ * <p>
+ * This is only available on {@link android.os.Build#isDebuggable debuggable} builds.
+ *
+ * @throws SecurityException if the build is not debuggable
+ *
+ * @hide
+ */
+ @TestApi
+ void clearOverrideSigningDetails();
+
+ /**
* @hide
*/
@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index c2f74a8..c9fd261 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -155,7 +155,8 @@
UserManager.DISALLOW_CONFIG_DEFAULT_APPS,
UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO,
UserManager.DISALLOW_SIM_GLOBALLY,
- UserManager.DISALLOW_ASSIST_CONTENT
+ UserManager.DISALLOW_ASSIST_CONTENT,
+ UserManager.DISALLOW_THREAD_NETWORK
});
public static final Set<String> DEPRECATED_USER_RESTRICTIONS = Sets.newArraySet(
@@ -206,7 +207,8 @@
UserManager.DISALLOW_ADD_WIFI_CONFIG,
UserManager.DISALLOW_CELLULAR_2G,
UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO,
- UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO
+ UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO,
+ UserManager.DISALLOW_THREAD_NETWORK
);
/**
@@ -252,7 +254,8 @@
UserManager.DISALLOW_ADD_WIFI_CONFIG,
UserManager.DISALLOW_CELLULAR_2G,
UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO,
- UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO
+ UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO,
+ UserManager.DISALLOW_THREAD_NETWORK
);
/**
diff --git a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
index 8d05450..55afb17 100644
--- a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
+++ b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
@@ -20,9 +20,12 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.content.pm.SigningDetails;
import android.os.Binder;
+import android.os.Build;
import android.os.UserHandle;
import android.util.ArrayMap;
+import android.util.apk.ApkSignatureVerifier;
import com.android.server.pm.Computer;
import com.android.server.pm.PackageManagerLocal;
@@ -72,6 +75,31 @@
mService.snapshotComputer(false /*allowLiveComputer*/), null);
}
+ @Override
+ public void addOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails,
+ @NonNull SigningDetails newSigningDetails) {
+ if (!Build.isDebuggable()) {
+ throw new SecurityException("This test API is only available on debuggable builds");
+ }
+ ApkSignatureVerifier.addOverrideSigningDetails(oldSigningDetails, newSigningDetails);
+ }
+
+ @Override
+ public void removeOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails) {
+ if (!Build.isDebuggable()) {
+ throw new SecurityException("This test API is only available on debuggable builds");
+ }
+ ApkSignatureVerifier.removeOverrideSigningDetails(oldSigningDetails);
+ }
+
+ @Override
+ public void clearOverrideSigningDetails() {
+ if (!Build.isDebuggable()) {
+ throw new SecurityException("This test API is only available on debuggable builds");
+ }
+ ApkSignatureVerifier.clearOverrideSigningDetails();
+ }
+
private abstract static class BaseSnapshotImpl implements AutoCloseable {
private boolean mClosed;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 5974ac8..266418f 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -4315,6 +4315,7 @@
boolean allowDuringSetup) {
if (allowDuringSetup || isUserSetupComplete()) {
mContext.startActivityAsUser(intent, bundle, handle);
+ dismissKeyboardShortcutsMenu();
} else {
Slog.i(TAG, "Not starting activity because user setup is in progress: " + intent);
}
@@ -4365,6 +4366,7 @@
if (statusbar != null) {
statusbar.showRecentApps(triggeredFromAltTab);
}
+ dismissKeyboardShortcutsMenu();
}
private void toggleKeyboardShortcutsMenu(int deviceId) {
diff --git a/services/core/java/com/android/server/rollback/OWNERS b/services/core/java/com/android/server/rollback/OWNERS
index daa0211..8337fd2 100644
--- a/services/core/java/com/android/server/rollback/OWNERS
+++ b/services/core/java/com/android/server/rollback/OWNERS
@@ -1,3 +1 @@
-ancr@google.com
-harshitmahajan@google.com
-robertogil@google.com
+include /services/core/java/com/android/server/crashrecovery/OWNERS
\ No newline at end of file
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index c3efcb1..885baf6 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -2691,6 +2691,7 @@
}
float maxDimAmount = getHighestDimAmountFromMap(wallpaper.mUidToDimAmount);
+ if (wallpaper.mWallpaperDimAmount == maxDimAmount) return;
wallpaper.mWallpaperDimAmount = maxDimAmount;
// Also set the dim amount to the lock screen wallpaper if the lock and home screen
// do not share the same wallpaper
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 8a38cc0..6fa6957 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -50,6 +50,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.activityTypeToString;
+import static android.app.WindowConfiguration.isFloating;
import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION;
import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE;
import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
@@ -75,6 +76,7 @@
import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
import static android.content.pm.ActivityInfo.FLAG_STATE_NOT_NEEDED;
import static android.content.pm.ActivityInfo.FLAG_TURN_SCREEN_ON;
+import static android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED;
import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
@@ -332,6 +334,7 @@
import android.service.dreams.DreamActivity;
import android.service.voice.IVoiceInteractionSession;
import android.util.ArraySet;
+import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
import android.util.MergedConfiguration;
@@ -7783,8 +7786,11 @@
@Override
void prepareSurfaces() {
- final boolean show = isVisible() || isAnimating(PARENTS,
- ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS
+ final boolean show = (isVisible()
+ // Ensure that the activity content is hidden when the decor surface is boosted to
+ // prevent UI redressing attack.
+ && !getTask().isDecorSurfaceBoosted())
+ || isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS
| ANIMATION_TYPE_PREDICT_BACK);
if (mSurfaceControl != null) {
@@ -8481,6 +8487,9 @@
// and back which can cause visible issues (see b/184078928).
final int parentWindowingMode =
newParentConfiguration.windowConfiguration.getWindowingMode();
+
+ applySizeOverrideIfNeeded(newParentConfiguration, parentWindowingMode, resolvedConfig);
+
final boolean isFixedOrientationLetterboxAllowed =
parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
|| parentWindowingMode == WINDOWING_MODE_FULLSCREEN
@@ -8580,6 +8589,87 @@
}
/**
+ * If necessary, override configuration fields related to app bounds.
+ * This will happen when the app is targeting SDK earlier than 35.
+ * The insets and configuration has decoupled since SDK level 35, to make the system
+ * compatible to existing apps, override the configuration with legacy metrics. In legacy
+ * metrics, fields such as appBounds will exclude some of the system bar areas.
+ * The override contains all potentially affected fields in Configuration, including
+ * screenWidthDp, screenHeightDp, smallestScreenWidthDp, and orientation.
+ * All overrides to those fields should be in this method.
+ */
+ private void applySizeOverrideIfNeeded(Configuration newParentConfiguration,
+ int parentWindowingMode, Configuration inOutConfig) {
+ if (mDisplayContent == null) {
+ return;
+ }
+ final Rect fullBounds = newParentConfiguration.windowConfiguration.getAppBounds();
+ int rotation = newParentConfiguration.windowConfiguration.getRotation();
+ if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming()) {
+ rotation = mDisplayContent.getRotation();
+ }
+ if (!mWmService.mFlags.mInsetsDecoupledConfiguration
+ || info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED)
+ || getCompatDisplayInsets() != null
+ || isFloating(parentWindowingMode) || fullBounds == null
+ || fullBounds.isEmpty() || rotation == ROTATION_UNDEFINED) {
+ // If the insets configuration decoupled logic is not enabled for the app, or the app
+ // already has a compat override, or the context doesn't contain enough info to
+ // calculate the override, skip the override.
+ return;
+ }
+
+ // Override starts here.
+ final Rect stableInsets = mDisplayContent.getDisplayPolicy().getDecorInsetsInfo(
+ rotation, fullBounds.width(), fullBounds.height()).mLegacyConfigInsets;
+ // This should be the only place override the configuration for ActivityRecord. Override
+ // the value if not calculated yet.
+ Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
+ if (outAppBounds == null || outAppBounds.isEmpty()) {
+ inOutConfig.windowConfiguration.setAppBounds(fullBounds);
+ outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
+ outAppBounds.inset(stableInsets);
+ }
+ float density = inOutConfig.densityDpi;
+ if (density == Configuration.DENSITY_DPI_UNDEFINED) {
+ density = newParentConfiguration.densityDpi;
+ }
+ density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
+ final int overrideScreenWidthDp = (int) (outAppBounds.width() / density + 0.5f);
+ inOutConfig.screenWidthDp =
+ Math.min(overrideScreenWidthDp, newParentConfiguration.screenWidthDp);
+ }
+ if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
+ final int overrideScreenHeightDp =
+ (int) (outAppBounds.height() / density + 0.5f);
+ inOutConfig.screenHeightDp =
+ Math.min(overrideScreenHeightDp, newParentConfiguration.screenHeightDp);
+ }
+ if (inOutConfig.smallestScreenWidthDp
+ == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED
+ && parentWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+ // For the case of PIP transition and multi-window environment, the
+ // smallestScreenWidthDp is handled already. Override only if the app is in
+ // fullscreen.
+ final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
+ DisplayInfo info = new DisplayInfo();
+ mDisplayContent.getDisplay().getDisplayInfo(info);
+ mDisplayContent.computeSizeRanges(info, rotated, info.logicalWidth,
+ info.logicalHeight, mDisplayContent.getDisplayMetrics().density,
+ inOutConfig, true /* legacyConfig */);
+ }
+
+ // It's possible that screen size will be considered in different orientation with or
+ // without considering the system bar insets. Override orientation as well.
+ if (inOutConfig.orientation == ORIENTATION_UNDEFINED) {
+ inOutConfig.orientation =
+ (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
+ ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
+ }
+ }
+
+ /**
* @return The orientation to use to understand if reachability is enabled.
*/
@Configuration.Orientation
@@ -8832,6 +8922,11 @@
if (mDisplayContent == null) {
return true;
}
+ if (mWmService.mFlags.mInsetsDecoupledConfiguration
+ && info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED)) {
+ // No insets should be considered any more.
+ return true;
+ }
// Only need to make changes if activity sets an orientation
final int requestedOrientation = getRequestedConfigurationOrientation();
if (requestedOrientation == ORIENTATION_UNDEFINED) {
@@ -8846,7 +8941,8 @@
: mDisplayContent.getDisplayInfo();
final Task task = getTask();
task.calculateInsetFrames(mTmpBounds /* outNonDecorBounds */,
- outStableBounds /* outStableBounds */, parentBounds /* bounds */, di);
+ outStableBounds /* outStableBounds */, parentBounds /* bounds */, di,
+ true /* useLegacyInsetsForStableBounds */);
final int orientationWithInsets = outStableBounds.height() >= outStableBounds.width()
? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
// If orientation does not match the orientation with insets applied, then a
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 44c5299..2fc6b5f 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -57,6 +57,7 @@
import static com.android.server.wm.ActivityRecord.State.PAUSING;
import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.ActivityRecord.State.STOPPING;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_IDLE;
@@ -104,6 +105,7 @@
import android.app.servertransaction.LaunchActivityItem;
import android.app.servertransaction.PauseActivityItem;
import android.app.servertransaction.ResumeActivityItem;
+import android.app.servertransaction.StopActivityItem;
import android.companion.virtual.VirtualDeviceManager;
import android.content.ComponentName;
import android.content.Context;
@@ -944,8 +946,10 @@
if (andResume) {
lifecycleItem = ResumeActivityItem.obtain(r.token, isTransitionForward,
r.shouldSendCompatFakeFocus());
- } else {
+ } else if (r.isVisibleRequested()) {
lifecycleItem = PauseActivityItem.obtain(r.token);
+ } else {
+ lifecycleItem = StopActivityItem.obtain(r.token);
}
// Schedule transaction.
@@ -1013,7 +1017,7 @@
// a resume.
r.setState(RESUMED, "realStartActivityLocked");
r.completeResumeLocked();
- } else {
+ } else if (r.isVisibleRequested()) {
// This activity is not starting in the resumed state... which should look like we asked
// it to pause+stop (but remain visible), and it has done so and reported back the
// current icicle and other state.
@@ -1021,6 +1025,9 @@
+ "(starting in paused state)", r);
r.setState(PAUSED, "realStartActivityLocked");
mRootWindowContainer.executeAppTransitionForAllDisplay();
+ } else {
+ // This activity is starting while invisible, so it should be stopped.
+ r.setState(STOPPING, "realStartActivityLocked");
}
// Perform OOM scoring after the activity state is set, so the process can be updated with
// the latest state.
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index e155126..e3ac35c 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -60,7 +60,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.server.wm.utils.InsetUtils;
import com.android.window.flags.Flags;
import java.io.PrintWriter;
@@ -1436,15 +1435,11 @@
return null;
}
final WindowState mainWindow = r.findMainWindow();
- Rect insets;
- if (mainWindow != null) {
- insets = mainWindow.getInsetsStateWithVisibilityOverride().calculateInsets(
- mBounds, WindowInsets.Type.tappableElement(),
- false /* ignoreVisibility */).toRect();
- InsetUtils.addInsets(insets, mainWindow.mActivityRecord.getLetterboxInsets());
- } else {
- insets = new Rect();
- }
+ final Rect insets = mainWindow != null
+ ? mainWindow.getInsetsStateWithVisibilityOverride().calculateInsets(
+ mBounds, WindowInsets.Type.tappableElement(),
+ false /* ignoreVisibility */).toRect()
+ : new Rect();
final int mode = mIsOpen ? MODE_OPENING : MODE_CLOSING;
mAnimationTarget = new RemoteAnimationTarget(t.mTaskId, mode, mCapturedLeash,
!r.fillsParent(), new Rect(),
@@ -1686,7 +1681,7 @@
|| (wallpaperController.getWallpaperTarget() != null
&& wallpaperController.wallpaperTransitionReady());
if (wallpaperReady && mPendingAnimation != null) {
- startAnimation();
+ mWindowManagerService.mAnimator.addAfterPrepareSurfacesRunnable(this::startAnimation);
}
}
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 071f403..f7baa79 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -18,10 +18,10 @@
import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityOptions.BackgroundActivityStartMode;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
-import static android.app.ActivityOptions.BackgroundActivityStartMode;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
@@ -37,12 +37,11 @@
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY;
import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
+import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
import static com.android.window.flags.Flags.balImproveRealCallerVisibilityCheck;
import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator;
import static com.android.window.flags.Flags.balRequireOptInSameUid;
-import static com.android.window.flags.Flags.balShowToasts;
import static com.android.window.flags.Flags.balShowToastsBlocked;
-import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import static java.util.Objects.requireNonNull;
@@ -752,7 +751,6 @@
Slog.wtf(TAG, "With Android 15 BAL hardening this activity start may be blocked"
+ " if the PI creator upgrades target_sdk to 35+! "
+ " (missing opt in by PI creator)!" + state.dump());
- showBalRiskToast();
return allowBasedOnCaller(state);
}
}
@@ -762,7 +760,6 @@
Slog.wtf(TAG, "With Android 14 BAL hardening this activity start will be blocked"
+ " if the PI sender upgrades target_sdk to 34+! "
+ " (missing opt in by PI sender)!" + state.dump());
- showBalRiskToast();
return allowBasedOnRealCaller(state);
}
}
@@ -793,7 +790,11 @@
private BalVerdict abortLaunch(BalState state) {
Slog.wtf(TAG, "Background activity launch blocked! "
+ state.dump());
- showBalBlockedToast();
+ if (balShowToastsBlocked()
+ && (state.mResultForCaller.allows() || state.mResultForRealCaller.allows())) {
+ // only show a toast if either caller or real caller could launch if they opted in
+ showToast("BAL blocked. go/debug-bal");
+ }
return statsLog(BalVerdict.BLOCK, state);
}
@@ -1192,18 +1193,6 @@
return true;
}
- private void showBalBlockedToast() {
- if (balShowToastsBlocked()) {
- showToast("BAL blocked. go/debug-bal");
- }
- }
-
- private void showBalRiskToast() {
- if (balShowToasts()) {
- showToast("BAL allowed in compat mode. go/debug-bal");
- }
- }
-
@VisibleForTesting void showToast(String toastText) {
UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
toastText, Toast.LENGTH_LONG).show());
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index a914c07..b616d24 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -107,7 +107,9 @@
ContentRecorder(@NonNull DisplayContent displayContent) {
this(displayContent, new RemoteMediaProjectionManagerWrapper(displayContent.mDisplayId),
- new DisplayManagerFlags().isConnectedDisplayManagementEnabled());
+ new DisplayManagerFlags().isConnectedDisplayManagementEnabled()
+ && !new DisplayManagerFlags()
+ .isPixelAnisotropyCorrectionInLogicalDisplayEnabled());
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index 7052982..a29cb60 100644
--- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -113,8 +113,10 @@
// Apply whole display info immediately as is if either:
// * it is the first display update
+ // * the display doesn't have visible content
// * shell transitions are disabled or temporary unavailable
if (displayInfoDiff == DIFF_EVERYTHING
+ || !mDisplayContent.getLastHasContent()
|| !mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
"DeferredDisplayUpdater: applying DisplayInfo immediately");
@@ -171,18 +173,25 @@
mDisplayContent.mInitialDisplayHeight);
final int fromRotation = mDisplayContent.getRotation();
- onStartCollect.run();
+ mDisplayContent.mAtmService.deferWindowLayout();
+ try {
+ onStartCollect.run();
- ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
- "DeferredDisplayUpdater: applied DisplayInfo after deferring");
+ ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
+ "DeferredDisplayUpdater: applied DisplayInfo after deferring");
- if (physicalDisplayUpdated) {
- onDisplayUpdated(transition, fromRotation, startBounds);
- } else {
- final TransitionRequestInfo.DisplayChange displayChange =
- getCurrentDisplayChange(fromRotation, startBounds);
- mDisplayContent.mTransitionController.requestStartTransition(transition,
- /* startTask= */ null, /* remoteTransition= */ null, displayChange);
+ if (physicalDisplayUpdated) {
+ onDisplayUpdated(transition, fromRotation, startBounds);
+ } else {
+ final TransitionRequestInfo.DisplayChange displayChange =
+ getCurrentDisplayChange(fromRotation, startBounds);
+ mDisplayContent.mTransitionController.requestStartTransition(transition,
+ /* startTask= */ null, /* remoteTransition= */ null, displayChange);
+ }
+ } finally {
+ // Run surface placement after requestStartTransition, so shell side can receive
+ // the transition request before handling task info changes.
+ mDisplayContent.mAtmService.continueWindowLayout();
}
});
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index da6db07..837d08b 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1319,7 +1319,7 @@
for (int i = 0; i < mChildren.size(); i++) {
SurfaceControl sc = mChildren.get(i).getSurfaceControl();
if (sc != null) {
- t.reparent(sc, mSurfaceControl);
+ t.reparent(sc, getParentingSurfaceControl());
}
}
@@ -2302,7 +2302,8 @@
mDisplayInfo.flags &= ~Display.FLAG_SCALING_DISABLED;
}
- computeSizeRanges(mDisplayInfo, rotated, dw, dh, mDisplayMetrics.density, outConfig);
+ computeSizeRanges(mDisplayInfo, rotated, dw, dh, mDisplayMetrics.density, outConfig,
+ false /* legacyConfig */);
setDisplayInfoOverride();
@@ -2438,7 +2439,8 @@
displayInfo.appHeight = appBounds.height();
final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(rotation);
displayInfo.displayCutout = displayCutout.isEmpty() ? null : displayCutout;
- computeSizeRanges(displayInfo, rotated, dw, dh, mDisplayMetrics.density, outConfig);
+ computeSizeRanges(displayInfo, rotated, dw, dh, mDisplayMetrics.density, outConfig,
+ false /* legacyConfig */);
return displayInfo;
}
@@ -2602,8 +2604,21 @@
return curSize;
}
- private void computeSizeRanges(DisplayInfo displayInfo, boolean rotated,
- int dw, int dh, float density, Configuration outConfig) {
+ /**
+ * Compute size range related fields of DisplayInfo and Configuration based on provided info.
+ * The fields usually contain word such as smallest or largest.
+ *
+ * @param displayInfo In-out display info to compute the result.
+ * @param rotated Whether the display is rotated.
+ * @param dw Display Width in current rotation.
+ * @param dh Display Height in current rotation.
+ * @param density Display density.
+ * @param outConfig The output configuration to
+ * @param legacyConfig Whether we need to report the legacy result, which is excluding system
+ * decorations.
+ */
+ void computeSizeRanges(DisplayInfo displayInfo, boolean rotated,
+ int dw, int dh, float density, Configuration outConfig, boolean legacyConfig) {
// We need to determine the smallest width that will occur under normal
// operation. To this, start with the base screen size and compute the
@@ -2621,10 +2636,10 @@
displayInfo.smallestNominalAppHeight = 1<<30;
displayInfo.largestNominalAppWidth = 0;
displayInfo.largestNominalAppHeight = 0;
- adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_0, unrotDw, unrotDh);
- adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_90, unrotDh, unrotDw);
- adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_180, unrotDw, unrotDh);
- adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_270, unrotDh, unrotDw);
+ adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_0, unrotDw, unrotDh, legacyConfig);
+ adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_90, unrotDh, unrotDw, legacyConfig);
+ adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_180, unrotDw, unrotDh, legacyConfig);
+ adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_270, unrotDh, unrotDw, legacyConfig);
if (outConfig == null) {
return;
@@ -2633,11 +2648,19 @@
(int) (displayInfo.smallestNominalAppWidth / density + 0.5f);
}
- private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int rotation, int dw, int dh) {
+ private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int rotation, int dw, int dh,
+ boolean legacyConfig) {
final DisplayPolicy.DecorInsets.Info info = mDisplayPolicy.getDecorInsetsInfo(
rotation, dw, dh);
- final int w = info.mConfigFrame.width();
- final int h = info.mConfigFrame.height();
+ final int w;
+ final int h;
+ if (!legacyConfig) {
+ w = info.mConfigFrame.width();
+ h = info.mConfigFrame.height();
+ } else {
+ w = info.mLegacyConfigFrame.width();
+ h = info.mLegacyConfigFrame.height();
+ }
if (w < displayInfo.smallestNominalAppWidth) {
displayInfo.smallestNominalAppWidth = w;
}
@@ -5805,6 +5828,21 @@
|| supportsSystemDecorations();
}
+ /**
+ * Returns the {@link SurfaceControl} where all the children should be parented on.
+ *
+ * <p> {@link DisplayContent} inserts a RootWrapper leash in the hierarchy above its original
+ * {@link #mSurfaceControl} and then overrides the {@link #mSurfaceControl} to point to the
+ * RootWrapper.
+ * <p> To prevent inconsistent state later where the DAs might get re-parented to the
+ * RootWrapper, this method should be used which returns the correct surface where the
+ * re-parenting should happen.
+ */
+ @Override
+ SurfaceControl getParentingSurfaceControl() {
+ return mWindowingLayer;
+ }
+
SurfaceControl getWindowingLayer() {
return mWindowingLayer;
}
@@ -6172,7 +6210,12 @@
* @param onDisplayChangeApplied callback that is called when the changes are applied
*/
void requestDisplayUpdate(@NonNull Runnable onDisplayChangeApplied) {
- mDisplayUpdater.updateDisplayInfo(onDisplayChangeApplied);
+ mAtmService.deferWindowLayout();
+ try {
+ mDisplayUpdater.updateDisplayInfo(onDisplayChangeApplied);
+ } finally {
+ mAtmService.continueWindowLayout();
+ }
}
void onDisplayInfoUpdated(@NonNull DisplayInfo newDisplayInfo) {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 57b9c63..e789fec 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1917,6 +1917,11 @@
*/
final Rect mConfigInsets = new Rect();
+ /**
+ * Legacy value of mConfigInsets for app compatibility purpose.
+ */
+ final Rect mLegacyConfigInsets = new Rect();
+
/** The display frame available after excluding {@link #mNonDecorInsets}. */
final Rect mNonDecorFrame = new Rect();
@@ -1927,6 +1932,11 @@
*/
final Rect mConfigFrame = new Rect();
+ /**
+ * Legacy value of mConfigFrame for app compatibility purpose.
+ */
+ final Rect mLegacyConfigFrame = new Rect();
+
private boolean mNeedUpdate = true;
InsetsState update(DisplayContent dc, int rotation, int w, int h) {
@@ -1937,15 +1947,26 @@
final Rect displayFrame = insetsState.getDisplayFrame();
final Insets decor = insetsState.calculateInsets(displayFrame,
dc.mWmService.mDecorTypes, true /* ignoreVisibility */);
- final Insets configInsets = insetsState.calculateInsets(displayFrame,
- dc.mWmService.mConfigTypes, true /* ignoreVisibility */);
+ final Insets configInsets = dc.mWmService.mConfigTypes == dc.mWmService.mDecorTypes
+ ? decor
+ : insetsState.calculateInsets(displayFrame, dc.mWmService.mConfigTypes,
+ true /* ignoreVisibility */);
+ final Insets legacyConfigInsets = dc.mWmService.mConfigTypes
+ == dc.mWmService.mLegacyConfigTypes
+ ? configInsets
+ : insetsState.calculateInsets(displayFrame,
+ dc.mWmService.mLegacyConfigTypes, true /* ignoreVisibility */);
mNonDecorInsets.set(decor.left, decor.top, decor.right, decor.bottom);
mConfigInsets.set(configInsets.left, configInsets.top, configInsets.right,
configInsets.bottom);
+ mLegacyConfigInsets.set(legacyConfigInsets.left, legacyConfigInsets.top,
+ legacyConfigInsets.right, legacyConfigInsets.bottom);
mNonDecorFrame.set(displayFrame);
mNonDecorFrame.inset(mNonDecorInsets);
mConfigFrame.set(displayFrame);
mConfigFrame.inset(mConfigInsets);
+ mLegacyConfigFrame.set(displayFrame);
+ mLegacyConfigFrame.inset(mLegacyConfigInsets);
mNeedUpdate = false;
return insetsState;
}
@@ -1953,8 +1974,10 @@
void set(Info other) {
mNonDecorInsets.set(other.mNonDecorInsets);
mConfigInsets.set(other.mConfigInsets);
+ mLegacyConfigInsets.set(other.mLegacyConfigInsets);
mNonDecorFrame.set(other.mNonDecorFrame);
mConfigFrame.set(other.mConfigFrame);
+ mLegacyConfigFrame.set(other.mLegacyConfigFrame);
mNeedUpdate = false;
}
@@ -2066,7 +2089,8 @@
final DecorInsets.Info newInfo = mDecorInsets.mTmpInfo;
final InsetsState newInsetsState = newInfo.update(mDisplayContent, rotation, dw, dh);
final DecorInsets.Info currentInfo = getDecorInsetsInfo(rotation, dw, dh);
- if (newInfo.mConfigFrame.equals(currentInfo.mConfigFrame)) {
+ if (newInfo.mConfigFrame.equals(currentInfo.mConfigFrame)
+ && newInfo.mLegacyConfigFrame.equals(currentInfo.mLegacyConfigFrame)) {
// Even if the config frame is not changed in current rotation, it may change the
// insets in other rotations if the frame of insets source is changed.
final InsetsState currentInsetsState = mDisplayContent.mDisplayFrames.mInsetsState;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index d87e21c..55dc30c 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3741,7 +3741,9 @@
wc.assignChildLayers(t);
if (!wc.needsZBoost()) {
// Place the decor surface under any untrusted content.
- if (mDecorSurfaceContainer != null && !decorSurfacePlaced
+ if (mDecorSurfaceContainer != null
+ && !mDecorSurfaceContainer.mIsBoosted
+ && !decorSurfacePlaced
&& shouldPlaceDecorSurfaceBelowContainer(wc)) {
mDecorSurfaceContainer.assignLayer(t, layer++);
decorSurfacePlaced = true;
@@ -3760,7 +3762,9 @@
}
// Place the decor surface just above the owner TaskFragment.
- if (mDecorSurfaceContainer != null && !decorSurfacePlaced
+ if (mDecorSurfaceContainer != null
+ && !mDecorSurfaceContainer.mIsBoosted
+ && !decorSurfacePlaced
&& wc == mDecorSurfaceContainer.mOwnerTaskFragment) {
mDecorSurfaceContainer.assignLayer(t, layer++);
decorSurfacePlaced = true;
@@ -3768,10 +3772,10 @@
}
}
- // If not placed yet, the decor surface should be on top of all non-boosted children.
- if (mDecorSurfaceContainer != null && !decorSurfacePlaced) {
+ // Boost the decor surface above other non-boosted windows if requested. The cover surface
+ // will ensure that the content of the windows below are invisible.
+ if (mDecorSurfaceContainer != null && mDecorSurfaceContainer.mIsBoosted) {
mDecorSurfaceContainer.assignLayer(t, layer++);
- decorSurfacePlaced = true;
}
for (int j = 0; j < mChildren.size(); ++j) {
@@ -3796,6 +3800,24 @@
return !isOwnActivity && !isTrustedTaskFragment;
}
+ void setDecorSurfaceBoosted(
+ @NonNull TaskFragment ownerTaskFragment,
+ boolean isBoosted,
+ @Nullable SurfaceControl.Transaction clientTransaction) {
+ if (mDecorSurfaceContainer == null
+ || mDecorSurfaceContainer.mOwnerTaskFragment != ownerTaskFragment) {
+ return;
+ }
+ mDecorSurfaceContainer.setBoosted(isBoosted, clientTransaction);
+ // scheduleAnimation() is called inside assignChildLayers(), which ensures that child
+ // surface visibility is updated with prepareSurfaces()
+ assignChildLayers();
+ }
+
+ boolean isDecorSurfaceBoosted() {
+ return mDecorSurfaceContainer != null && mDecorSurfaceContainer.mIsBoosted;
+ }
+
boolean isTaskId(int taskId) {
return mTaskId == taskId;
}
@@ -6796,14 +6818,35 @@
}
/**
- * A decor surface that is requested by a {@code TaskFragmentOrganizer} which will be placed
- * below children windows except for own Activities and TaskFragment in fully trusted mode.
+ * A class managing the decor surface.
+ *
+ * A decor surface is requested by a {@link TaskFragmentOrganizer} and is placed below children
+ * windows in the Task except for own Activities and TaskFragments in fully trusted mode. The
+ * decor surface is created and shared with the client app with
+ * {@link android.window.TaskFragmentOperation#OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE} and
+ * be removed with
+ * {@link android.window.TaskFragmentOperation#OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE}.
+ *
+ * When boosted with
+ * {@link android.window.TaskFragmentOperation#OP_TYPE_SET_DECOR_SURFACE_BOOSTED}, the decor
+ * surface is placed above all non-boosted windows in the Task, but all the content below it
+ * will be hidden to prevent UI redressing attacks. This can be used by the draggable
+ * divider between {@link TaskFragment}s where veils are drawn on the decor surface while
+ * dragging to indicate new bounds.
*/
@VisibleForTesting
class DecorSurfaceContainer {
+
+ // The container surface is the parent of the decor surface. The container surface
+ // should NEVER be shared with the client. It is used to ensure that the decor surface has
+ // a z-order in the Task that is managed by WM core and cannot be updated by the client
+ // process.
@VisibleForTesting
@NonNull final SurfaceControl mContainerSurface;
+ // The decor surface is shared with the client process owning the
+ // {@link TaskFragmentOrganizer}. It can be used to draw the divider between TaskFragments
+ // or other decorations.
@VisibleForTesting
@NonNull final SurfaceControl mDecorSurface;
@@ -6812,12 +6855,18 @@
@VisibleForTesting
@NonNull TaskFragment mOwnerTaskFragment;
+ private boolean mIsBoosted;
+
+ // The surface transactions that will be applied when the layer is reassigned.
+ @NonNull private final List<SurfaceControl.Transaction> mPendingClientTransactions =
+ new ArrayList<>();
+
private DecorSurfaceContainer(@NonNull TaskFragment initialOwner) {
mOwnerTaskFragment = initialOwner;
mContainerSurface = makeSurface().setContainerLayer()
.setParent(mSurfaceControl)
.setName(mSurfaceControl + " - decor surface container")
- .setEffectLayer()
+ .setContainerLayer()
.setHidden(false)
.setCallsite("Task.DecorSurfaceContainer")
.build();
@@ -6830,14 +6879,28 @@
.build();
}
+ private void setBoosted(
+ boolean isBoosted, @Nullable SurfaceControl.Transaction clientTransaction) {
+ mIsBoosted = isBoosted;
+ // The client transaction will be applied together with the next assignLayer.
+ if (clientTransaction != null) {
+ mDecorSurfaceContainer.mPendingClientTransactions.add(clientTransaction);
+ }
+ }
+
private void assignLayer(@NonNull SurfaceControl.Transaction t, int layer) {
t.setLayer(mContainerSurface, layer);
t.setVisibility(mContainerSurface, mOwnerTaskFragment.isVisible());
+ for (int i = 0; i < mPendingClientTransactions.size(); i++) {
+ t.merge(mPendingClientTransactions.get(i));
+ }
+ mPendingClientTransactions.clear();
}
private void release() {
- mDecorSurface.release();
- mContainerSurface.release();
+ getSyncTransaction()
+ .remove(mDecorSurface)
+ .remove(mContainerSurface);
}
}
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 597e901..3cf561c 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -996,8 +996,7 @@
} else {
// Still have something resumed; can't sleep until it is paused.
ProtoLog.v(WM_DEBUG_STATES, "Sleep needs to pause %s", mResumedActivity);
- startPausing(false /* userLeaving */, true /* uiSleeping */, null /* resuming */,
- "sleep");
+ startPausing(true /* uiSleeping */, null /* resuming */, "sleep");
}
shouldSleep = false;
} else if (mPausingActivity != null) {
@@ -2307,7 +2306,8 @@
// area, i.e. the screen area without the system bars.
// The non decor inset are areas that could never be removed in Honeycomb. See
// {@link WindowManagerPolicy#getNonDecorInsetsLw}.
- calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di);
+ calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di,
+ false /* useLegacyInsetsForStableBounds */);
} else {
// Apply the given non-decor and stable insets to calculate the corresponding bounds
// for screen size of configuration.
@@ -2405,9 +2405,11 @@
* @param outNonDecorBounds where to place bounds with non-decor insets applied.
* @param outStableBounds where to place bounds with stable insets applied.
* @param bounds the bounds to inset.
+ * @param useLegacyInsetsForStableBounds {@code true} if we need to use the legacy insets frame
+ * for apps targeting U or before when calculating stable bounds.
*/
void calculateInsetFrames(Rect outNonDecorBounds, Rect outStableBounds, Rect bounds,
- DisplayInfo displayInfo) {
+ DisplayInfo displayInfo, boolean useLegacyInsetsForStableBounds) {
outNonDecorBounds.set(bounds);
outStableBounds.set(bounds);
if (mDisplayContent == null) {
@@ -2419,7 +2421,11 @@
final DisplayPolicy.DecorInsets.Info info = policy.getDecorInsetsInfo(
displayInfo.rotation, displayInfo.logicalWidth, displayInfo.logicalHeight);
intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, info.mNonDecorInsets);
- intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mConfigInsets);
+ if (!useLegacyInsetsForStableBounds) {
+ intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mConfigInsets);
+ } else {
+ intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mLegacyConfigInsets);
+ }
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index fd0289e..f2af852 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -716,7 +716,7 @@
// If parent is null, the layer should be placed offscreen so reparent to null. Otherwise,
// set to the available parent.
- t.reparent(mSurfaceControl, mParent == null ? null : mParent.getSurfaceControl());
+ t.reparent(mSurfaceControl, mParent == null ? null : mParent.getParentingSurfaceControl());
if (mLastRelativeToLayer != null) {
t.setRelativeLayer(mSurfaceControl, mLastRelativeToLayer, mLastLayer);
@@ -2907,6 +2907,17 @@
}
/**
+ * Returns the {@link SurfaceControl} where all the children should be parented on.
+ *
+ * A {@link WindowContainer} might insert intermediate leashes in the hierarchy and hence
+ * {@link #getSurfaceControl} won't return the correct surface where the children should be
+ * re-parented on.
+ */
+ SurfaceControl getParentingSurfaceControl() {
+ return getSurfaceControl();
+ }
+
+ /**
* Use this method instead of {@link #getPendingTransaction()} if the Transaction should be
* synchronized with the client.
*
diff --git a/services/core/java/com/android/server/wm/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java
index 7b0d931..294733e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerFlags.java
+++ b/services/core/java/com/android/server/wm/WindowManagerFlags.java
@@ -48,5 +48,7 @@
final boolean mAllowsScreenSizeDecoupledFromStatusBarAndCutout =
Flags.allowsScreenSizeDecoupledFromStatusBarAndCutout();
+ final boolean mInsetsDecoupledConfiguration = Flags.insetsDecoupledConfiguration();
+
/* End Available Flags */
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d4cbb6a..71ffabf 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -561,6 +561,8 @@
/** Device default insets types shall be excluded from config app sizes. */
final int mConfigTypes;
+ final int mLegacyConfigTypes;
+
final boolean mLimitedAlphaCompositing;
final int mMaxUiWidth;
@@ -1190,13 +1192,23 @@
final boolean isScreenSizeDecoupledFromStatusBarAndCutout = context.getResources()
.getBoolean(R.bool.config_decoupleStatusBarAndDisplayCutoutFromScreenSize)
&& mFlags.mAllowsScreenSizeDecoupledFromStatusBarAndCutout;
- if (!isScreenSizeDecoupledFromStatusBarAndCutout) {
+ if (mFlags.mInsetsDecoupledConfiguration) {
+ mDecorTypes = 0;
+ mConfigTypes = 0;
+ } else if (isScreenSizeDecoupledFromStatusBarAndCutout) {
+ mDecorTypes = WindowInsets.Type.navigationBars();
+ mConfigTypes = WindowInsets.Type.navigationBars();
+ } else {
mDecorTypes = WindowInsets.Type.displayCutout() | WindowInsets.Type.navigationBars();
mConfigTypes = WindowInsets.Type.displayCutout() | WindowInsets.Type.statusBars()
| WindowInsets.Type.navigationBars();
+ }
+ if (isScreenSizeDecoupledFromStatusBarAndCutout) {
+ // Do not fallback to legacy value for enabled devices.
+ mLegacyConfigTypes = WindowInsets.Type.navigationBars();
} else {
- mDecorTypes = WindowInsets.Type.navigationBars();
- mConfigTypes = WindowInsets.Type.navigationBars();
+ mLegacyConfigTypes = WindowInsets.Type.displayCutout() | WindowInsets.Type.statusBars()
+ | WindowInsets.Type.navigationBars();
}
mLetterboxConfiguration = new LetterboxConfiguration(
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index a63e106..7e6f5ac 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -34,6 +34,7 @@
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_COMPANION_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_DECOR_SURFACE_BOOSTED;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH;
@@ -124,6 +125,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
import com.android.server.pm.LauncherAppsService.LauncherAppsServiceInternal;
+import com.android.window.flags.Flags;
import java.util.ArrayList;
import java.util.HashMap;
@@ -1557,13 +1559,11 @@
break;
}
case OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE: {
- final Task task = taskFragment.getTask();
- task.moveOrCreateDecorSurfaceFor(taskFragment);
+ taskFragment.getTask().moveOrCreateDecorSurfaceFor(taskFragment);
break;
}
case OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE: {
- final Task task = taskFragment.getTask();
- task.removeDecorSurface();
+ taskFragment.getTask().removeDecorSurface();
break;
}
case OP_TYPE_SET_DIM_ON_TASK: {
@@ -1577,6 +1577,23 @@
operation.isMoveToBottomIfClearWhenLaunch());
break;
}
+ case OP_TYPE_SET_DECOR_SURFACE_BOOSTED: {
+ if (Flags.activityEmbeddingInteractiveDividerFlag()) {
+ final SurfaceControl.Transaction clientTransaction =
+ operation.getSurfaceTransaction();
+ if (clientTransaction != null) {
+ // Sanitize the client transaction. sanitize() silently removes invalid
+ // operations and does not throw or provide signal about whether there are
+ // any invalid operations.
+ clientTransaction.sanitize(caller.mPid, caller.mUid);
+ }
+ taskFragment.getTask().setDecorSurfaceBoosted(
+ taskFragment,
+ operation.getBooleanValue() /* isBoosted */,
+ clientTransaction);
+ }
+ break;
+ }
}
return effects;
}
@@ -1616,19 +1633,6 @@
return false;
}
- // TODO (b/293654166) remove the decor surface checks once we clear security reviews
- if ((opType == OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE
- || opType == OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE)
- && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
- final Throwable exception = new SecurityException(
- "Only a system organizer can perform OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE"
- + " or OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE."
- );
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
- opType, exception);
- return false;
- }
-
if ((opType == OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH)
&& !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
final Throwable exception = new SecurityException(
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 3607ddd..7a710dc 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -40,6 +40,7 @@
"com_android_server_biometrics_SurfaceToNativeHandleConverter.cpp",
"com_android_server_ConsumerIrService.cpp",
"com_android_server_companion_virtual_InputController.cpp",
+ "com_android_server_companion_virtual_VirtualDeviceImpl.cpp",
"com_android_server_devicepolicy_CryptoTestHelper.cpp",
"com_android_server_display_DisplayControl.cpp",
"com_android_server_display_SmallAreaDetectionController.cpp",
@@ -214,6 +215,7 @@
static_libs: [
"android.hardware.broadcastradio@common-utils-1x-lib",
"libaidlcommonsupport",
+ "libvirtualdevicebuildflags",
],
product_variables: {
diff --git a/services/core/jni/com_android_server_companion_virtual_VirtualDeviceImpl.cpp b/services/core/jni/com_android_server_companion_virtual_VirtualDeviceImpl.cpp
new file mode 100644
index 0000000..1e6a9db
--- /dev/null
+++ b/services/core/jni/com_android_server_companion_virtual_VirtualDeviceImpl.cpp
@@ -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.
+ */
+
+#include <android_companion_virtualdevice_build_flags.h>
+#include <nativehelper/JNIHelp.h>
+
+#include <array>
+
+#include "jni.h"
+
+namespace android {
+namespace {
+
+jboolean nativeVirtualCameraServiceBuildFlagEnabled(JNIEnv* env, jobject clazz) {
+ return ::android::companion::virtualdevice::flags::virtual_camera_service_build_flag();
+}
+
+const std::array<JNINativeMethod, 1> kMethods = {
+ {{"nativeVirtualCameraServiceBuildFlagEnabled", "()Z",
+ (void*)nativeVirtualCameraServiceBuildFlagEnabled}},
+};
+
+} // namespace
+
+int register_android_server_companion_virtual_VirtualDeviceImpl(JNIEnv* env) {
+ return jniRegisterNativeMethods(env, "com/android/server/companion/virtual/VirtualDeviceImpl",
+ kMethods.data(), kMethods.size());
+}
+
+} // namespace android
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index c778398..610fcb5 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -502,7 +502,7 @@
toString(mLocked.pointerGesturesEnabled));
dump += StringPrintf(INDENT "Show Touches: %s\n", toString(mLocked.showTouches));
dump += StringPrintf(INDENT "Pointer Capture: %s, seq=%" PRIu32 "\n",
- mLocked.pointerCaptureRequest.enable ? "Enabled" : "Disabled",
+ mLocked.pointerCaptureRequest.isEnable() ? "Enabled" : "Disabled",
mLocked.pointerCaptureRequest.seq);
if (auto pc = mLocked.legacyPointerController.lock(); pc) {
dump += pc->dump();
@@ -1717,7 +1717,7 @@
return;
}
- ALOGV("%s pointer capture.", request.enable ? "Enabling" : "Disabling");
+ ALOGV("%s pointer capture.", request.isEnable() ? "Enabling" : "Disabling");
mLocked.pointerCaptureRequest = request;
} // release lock
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 0936888..6464081 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -65,6 +65,7 @@
int register_android_server_stats_pull_StatsPullAtomService(JNIEnv* env);
int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env);
int register_android_server_companion_virtual_InputController(JNIEnv* env);
+int register_android_server_companion_virtual_VirtualDeviceImpl(JNIEnv* env);
int register_android_server_app_GameManagerService(JNIEnv* env);
int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env);
int register_com_android_server_display_DisplayControl(JNIEnv* env);
@@ -128,6 +129,7 @@
register_android_server_stats_pull_StatsPullAtomService(env);
register_android_server_sensor_SensorService(vm, env);
register_android_server_companion_virtual_InputController(env);
+ register_android_server_companion_virtual_VirtualDeviceImpl(env);
register_android_server_app_GameManagerService(env);
register_com_android_server_wm_TaskFpsCallbackController(env);
register_com_android_server_display_DisplayControl(env);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c37946b..ee72db0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3395,7 +3395,9 @@
if (shouldMigrateV1ToDevicePolicyEngine()) {
migrateV1PoliciesToDevicePolicyEngine();
}
+ maybeMigratePoliciesPostUpgradeToDevicePolicyEngineLocked();
migratePoliciesToPolicyEngineLocked();
+
}
maybeStartSecurityLogMonitorOnActivityManagerReady();
break;
@@ -16877,6 +16879,8 @@
private int checkDeviceOwnerProvisioningPreCondition(@UserIdInt int callingUserId) {
synchronized (getLockObject()) {
final int deviceOwnerUserId = mInjector.userManagerIsHeadlessSystemUserMode()
+ && (!Flags.headlessDeviceOwnerProvisioningFixEnabled()
+ || getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_AFFILIATED)
? UserHandle.USER_SYSTEM
: callingUserId;
Slogf.i(LOG_TAG, "Calling user %d, device owner will be set on user %d",
@@ -21549,10 +21553,21 @@
setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime());
setLocale(provisioningParams.getLocale());
+
+
+ boolean isSingleUserMode;
+ if (Flags.headlessDeviceOwnerProvisioningFixEnabled()) {
+ DeviceAdminInfo adminInfo = findAdmin(
+ deviceAdmin, caller.getUserId(), /* throwForMissingPermission= */ false);
+ isSingleUserMode = (adminInfo != null && adminInfo.getHeadlessDeviceOwnerMode()
+ == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER);
+ } else {
+ isSingleUserMode =
+ (getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER);
+ }
int deviceOwnerUserId = Flags.headlessDeviceOwnerSingleUserEnabled()
- && getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER
- ? mUserManagerInternal.getMainUserId()
- : UserHandle.USER_SYSTEM;
+ && isSingleUserMode
+ ? mUserManagerInternal.getMainUserId() : UserHandle.USER_SYSTEM;
if (!removeNonRequiredAppsForManagedDevice(
deviceOwnerUserId,
@@ -23736,7 +23751,9 @@
if (!canForceMigration && !shouldMigrateV1ToDevicePolicyEngine()) {
return false;
}
- return migrateV1PoliciesToDevicePolicyEngine();
+ boolean migrated = migrateV1PoliciesToDevicePolicyEngine();
+ migrated &= migratePoliciesPostUpgradeToDevicePolicyEngineLocked();
+ return migrated;
});
}
@@ -23765,6 +23782,30 @@
/**
* Migrates the initial set of policies to use policy engine.
+ * [b/318497672] Migrate policies that weren't migrated properly in the initial migration on
+ * update from Android T to Android U
+ */
+ private void maybeMigratePoliciesPostUpgradeToDevicePolicyEngineLocked() {
+ if (!mOwners.isMigratedToPolicyEngine() || mOwners.isMigratedPostUpdate()) {
+ return;
+ }
+ migratePoliciesPostUpgradeToDevicePolicyEngineLocked();
+ mOwners.markPostUpgradeMigration();
+ }
+
+ private boolean migratePoliciesPostUpgradeToDevicePolicyEngineLocked() {
+ try {
+ migrateScreenCapturePolicyLocked();
+ migrateLockTaskPolicyLocked();
+ return true;
+ } catch (Exception e) {
+ Slogf.e(LOG_TAG, e, "Error occurred during post upgrade migration to the device "
+ + "policy engine.");
+ return false;
+ }
+ }
+
+ /**
* @return {@code true} if policies were migrated successfully, {@code false} otherwise.
*/
private boolean migrateV1PoliciesToDevicePolicyEngine() {
@@ -23777,7 +23818,6 @@
migrateAutoTimezonePolicy();
migratePermissionGrantStatePolicies();
}
- migrateScreenCapturePolicyLocked();
migratePermittedInputMethodsPolicyLocked();
migrateAccountManagementDisabledPolicyLocked();
migrateUserControlDisabledPackagesLocked();
@@ -23858,14 +23898,12 @@
private void migrateScreenCapturePolicyLocked() {
Binder.withCleanCallingIdentity(() -> {
- if (mPolicyCache.getScreenCaptureDisallowedUser() == UserHandle.USER_NULL) {
- return;
- }
ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
if (admin != null
&& ((isDeviceOwner(admin) && admin.disableScreenCapture)
|| (admin.getParentActiveAdmin() != null
&& admin.getParentActiveAdmin().disableScreenCapture))) {
+
EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
admin.info.getComponent(),
admin.getUserHandle().getIdentifier(),
@@ -23894,6 +23932,48 @@
});
}
+ private void migrateLockTaskPolicyLocked() {
+ Binder.withCleanCallingIdentity(() -> {
+ ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+ if (deviceOwner != null) {
+ int doUserId = deviceOwner.getUserHandle().getIdentifier();
+ DevicePolicyData policies = getUserData(doUserId);
+ List<String> packages = policies.mLockTaskPackages;
+ int features = policies.mLockTaskFeatures;
+ // TODO: find out about persistent preferred activities
+ if (!packages.isEmpty()) {
+ setLockTaskPolicyInPolicyEngine(deviceOwner, doUserId, packages, features);
+ }
+ }
+
+ for (int userId : mUserManagerInternal.getUserIds()) {
+ ActiveAdmin profileOwner = getProfileOwnerLocked(userId);
+ if (profileOwner != null && canDPCManagedUserUseLockTaskLocked(userId)) {
+ DevicePolicyData policies = getUserData(userId);
+ List<String> packages = policies.mLockTaskPackages;
+ int features = policies.mLockTaskFeatures;
+ if (!packages.isEmpty()) {
+ setLockTaskPolicyInPolicyEngine(profileOwner, userId, packages, features);
+ }
+ }
+ }
+ });
+ }
+
+ private void setLockTaskPolicyInPolicyEngine(
+ ActiveAdmin admin, int userId, List<String> packages, int features) {
+ EnforcingAdmin enforcingAdmin =
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ admin.info.getComponent(),
+ userId,
+ admin);
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.LOCK_TASK,
+ enforcingAdmin,
+ new LockTaskPolicy(new HashSet<>(packages), features),
+ userId);
+ }
+
private void migratePermittedInputMethodsPolicyLocked() {
Binder.withCleanCallingIdentity(() -> {
List<UserInfo> users = mUserManager.getUsers();
@@ -24256,4 +24336,13 @@
return mDevicePolicyEngine.getMaxPolicyStorageLimit();
}
+
+ @Override
+ public int getHeadlessDeviceOwnerMode(String callerPackageName) {
+ final CallerIdentity caller = getCallerIdentity(callerPackageName);
+ enforcePermission(MANAGE_PROFILE_AND_DEVICE_OWNERS, caller.getPackageName(),
+ caller.getUserId());
+
+ return Binder.withCleanCallingIdentity(() -> getHeadlessDeviceOwnerMode());
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index c5a9888..7912cbc 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -623,12 +623,25 @@
}
}
+ void markPostUpgradeMigration() {
+ synchronized (mData) {
+ mData.mPoliciesMigratedPostUpdate = true;
+ mData.writeDeviceOwner();
+ }
+ }
+
boolean isSecurityLoggingMigrated() {
synchronized (mData) {
return mData.mSecurityLoggingMigrated;
}
}
+ boolean isMigratedPostUpdate() {
+ synchronized (mData) {
+ return mData.mPoliciesMigratedPostUpdate;
+ }
+ }
+
@GuardedBy("mData")
void pushToAppOpsLocked() {
if (!mSystemReady) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index 9d73ed0..42ac998 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -89,6 +89,8 @@
private static final String ATTR_MIGRATED_TO_POLICY_ENGINE = "migratedToPolicyEngine";
private static final String ATTR_SECURITY_LOG_MIGRATED = "securityLogMigrated";
+ private static final String ATTR_MIGRATED_POST_UPGRADE = "migratedPostUpgrade";
+
// Internal state for the device owner package.
OwnerInfo mDeviceOwner;
int mDeviceOwnerUserId = UserHandle.USER_NULL;
@@ -117,6 +119,8 @@
boolean mMigratedToPolicyEngine = false;
boolean mSecurityLoggingMigrated = false;
+ boolean mPoliciesMigratedPostUpdate = false;
+
OwnersData(PolicyPathProvider pathProvider) {
mPathProvider = pathProvider;
}
@@ -400,6 +404,7 @@
out.startTag(null, TAG_POLICY_ENGINE_MIGRATION);
out.attributeBoolean(null, ATTR_MIGRATED_TO_POLICY_ENGINE, mMigratedToPolicyEngine);
+ out.attributeBoolean(null, ATTR_MIGRATED_POST_UPGRADE, mPoliciesMigratedPostUpdate);
if (Flags.securityLogV2Enabled()) {
out.attributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, mSecurityLoggingMigrated);
}
@@ -463,8 +468,11 @@
case TAG_POLICY_ENGINE_MIGRATION:
mMigratedToPolicyEngine = parser.getAttributeBoolean(
null, ATTR_MIGRATED_TO_POLICY_ENGINE, false);
+ mPoliciesMigratedPostUpdate = parser.getAttributeBoolean(
+ null, ATTR_MIGRATED_POST_UPGRADE, false);
mSecurityLoggingMigrated = Flags.securityLogV2Enabled()
&& parser.getAttributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, false);
+
break;
default:
Slog.e(TAG, "Unexpected tag: " + tag);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 71facab..e713a82 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -506,6 +506,10 @@
UserManager.DISALLOW_SIM_GLOBALLY,
POLICY_FLAG_GLOBAL_ONLY_POLICY);
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ASSIST_CONTENT, /* flags= */ 0);
+ if (com.android.net.thread.platform.flags.Flags.threadUserRestrictionEnabled()) {
+ USER_RESTRICTION_FLAGS.put(
+ UserManager.DISALLOW_THREAD_NETWORK, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+ }
for (String key : USER_RESTRICTION_FLAGS.keySet()) {
createAndAddUserRestrictionPolicyDefinition(key, USER_RESTRICTION_FLAGS.get(key));
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 2112dae..c6189ed 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -161,6 +161,7 @@
import com.android.server.notification.NotificationManagerService;
import com.android.server.oemlock.OemLockService;
import com.android.server.om.OverlayManagerService;
+import com.android.server.ondeviceintelligence.OnDeviceIntelligenceManagerService;
import com.android.server.os.BugreportManagerService;
import com.android.server.os.DeviceIdentifiersPolicyService;
import com.android.server.os.NativeTombstoneManagerService;
@@ -1134,7 +1135,7 @@
ServiceManager.addService(Context.PLATFORM_COMPAT_SERVICE, platformCompat);
ServiceManager.addService(Context.PLATFORM_COMPAT_NATIVE_SERVICE,
new PlatformCompatNative(platformCompat));
- AppCompatCallbacks.install(new long[0]);
+ AppCompatCallbacks.install(new long[0], new long[0]);
t.traceEnd();
// FileIntegrityService responds to requests from apps and the system. It needs to run after
@@ -1965,6 +1966,7 @@
startSystemCaptionsManagerService(context, t);
startTextToSpeechManagerService(context, t);
startWearableSensingService(t);
+ startOnDeviceIntelligenceService(t);
if (deviceHasConfigString(
context, R.string.config_defaultAmbientContextDetectionService)) {
@@ -3335,6 +3337,12 @@
t.traceEnd(); // startOtherServices
}
+ private void startOnDeviceIntelligenceService(TimingsTraceAndSlog t) {
+ t.traceBegin("startOnDeviceIntelligenceManagerService");
+ mSystemServiceManager.startService(OnDeviceIntelligenceManagerService.class);
+ t.traceEnd();
+ }
+
/**
* Starts system services defined in apexes.
*
diff --git a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
index acaec21..fd2e8c8 100644
--- a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
@@ -27,9 +27,11 @@
import com.android.server.SystemConfig
import com.android.server.SystemService
import com.android.server.appop.AppOpsCheckingServiceInterface
+import com.android.server.permission.PermissionManagerLocal
import com.android.server.permission.access.appop.AppOpService
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.permission.PermissionManagerLocalImpl
import com.android.server.permission.access.permission.PermissionService
import com.android.server.pm.KnownPackages
import com.android.server.pm.PackageManagerLocal
@@ -63,6 +65,11 @@
LocalServices.addService(AppOpsCheckingServiceInterface::class.java, appOpService)
LocalServices.addService(PermissionManagerServiceInterface::class.java, permissionService)
+
+ LocalManagerRegistry.addManager(
+ PermissionManagerLocal::class.java,
+ PermissionManagerLocalImpl(this)
+ )
}
fun initialize() {
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index 67df67f..af8ce31 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -63,6 +63,12 @@
private val privilegedPermissionAllowlistViolations = MutableIndexedSet<String>()
+ /**
+ * Test-only switch to enforce signature permission allowlist even on debuggable builds.
+ */
+ @Volatile
+ var isSignaturePermissionAllowlistForceEnforced = false
+
override val subjectScheme: String
get() = UidUri.SCHEME
@@ -1274,7 +1280,7 @@
SigningDetails.CertCapabilities.PERMISSION
)
if (!Flags.signaturePermissionAllowlistEnabled()) {
- return hasCommonSigner;
+ return hasCommonSigner
}
if (!hasCommonSigner) {
return false
@@ -1308,7 +1314,7 @@
" ${packageState.packageName} (${packageState.path}) not in" +
" signature permission allowlist"
)
- if (!Build.isDebuggable()) {
+ if (!Build.isDebuggable() || isSignaturePermissionAllowlistForceEnforced) {
return false
}
}
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionManagerLocalImpl.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionManagerLocalImpl.kt
new file mode 100644
index 0000000..ad2d70bb
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionManagerLocalImpl.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.permission.access.permission
+
+import android.os.Build
+import com.android.server.permission.PermissionManagerLocal
+import com.android.server.permission.access.AccessCheckingService
+import com.android.server.permission.access.PermissionUri
+import com.android.server.permission.access.UidUri
+
+class PermissionManagerLocalImpl(
+ private val service: AccessCheckingService
+) : PermissionManagerLocal {
+ private val policy =
+ service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as AppIdPermissionPolicy
+
+ override fun isSignaturePermissionAllowlistForceEnforced(): Boolean {
+ check(Build.isDebuggable())
+ return policy.isSignaturePermissionAllowlistForceEnforced
+ }
+
+ override fun setSignaturePermissionAllowlistForceEnforced(forceEnforced: Boolean) {
+ check(Build.isDebuggable())
+ policy.isSignaturePermissionAllowlistForceEnforced = forceEnforced
+ }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
index a33e52f..e5d3153 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
@@ -91,8 +91,8 @@
@Test
public void testSoftInputShowHideHistoryDump_withNulls_doesntThrow() {
var writer = new StringWriter();
- var history = new InputMethodManagerService.SoftInputShowHideHistory();
- history.addEntry(new InputMethodManagerService.SoftInputShowHideHistory.Entry(
+ var history = new SoftInputShowHideHistory();
+ history.addEntry(new SoftInputShowHideHistory.Entry(
null,
null,
null,
diff --git a/services/tests/VpnTests/Android.bp b/services/tests/VpnTests/Android.bp
index 64a9a3b..a5011a8 100644
--- a/services/tests/VpnTests/Android.bp
+++ b/services/tests/VpnTests/Android.bp
@@ -17,8 +17,7 @@
"java/**/*.java",
"java/**/*.kt",
],
-
- defaults: ["framework-connectivity-test-defaults"],
+ sdk_version: "core_platform", // tests can use @CorePlatformApi's
test_suites: ["device-tests"],
static_libs: [
"androidx.test.rules",
@@ -32,6 +31,13 @@
"service-connectivity-tiramisu-pre-jarjar",
],
libs: [
+ // order matters: classes in framework-connectivity are resolved before framework,
+ // meaning @hide APIs in framework-connectivity are resolved before @SystemApi
+ // stubs in framework
+ "framework-connectivity.impl",
+ "framework-connectivity-t.impl",
+ "framework",
+ "framework-res",
"android.test.runner",
"android.test.base",
"android.test.mock",
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
index dc6abf1..1c71abc 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
@@ -52,7 +52,9 @@
private static final int WIDTH = 500;
private static final int HEIGHT = 900;
private static final Point PORTRAIT_SIZE = new Point(WIDTH, HEIGHT);
+ private static final Point PORTRAIT_DOUBLE_WIDTH = new Point(2 * WIDTH, HEIGHT);
private static final Point LANDSCAPE_SIZE = new Point(HEIGHT, WIDTH);
+ private static final Point LANDSCAPE_DOUBLE_HEIGHT = new Point(HEIGHT, 2 * WIDTH);
@Mock
private SurfaceControl.Transaction mMockTransaction;
@@ -69,6 +71,16 @@
}
@Test
+ public void testGetDisplaySurfaceDefaultSizeLocked_notRotated_anisotropyCorrection() {
+ mDisplayDeviceInfo.xDpi = 0.5f;
+ mDisplayDeviceInfo.yDpi = 1.0f;
+ DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
+ mMockDisplayAdapter, /*isAnisotropyCorrectionEnabled=*/ true);
+ assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(
+ PORTRAIT_DOUBLE_WIDTH);
+ }
+
+ @Test
public void testGetDisplaySurfaceDefaultSizeLocked_notRotated() {
DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
mMockDisplayAdapter);
@@ -84,6 +96,17 @@
}
@Test
+ public void testGetDisplaySurfaceDefaultSizeLocked_rotation90_anisotropyCorrection() {
+ mDisplayDeviceInfo.xDpi = 0.5f;
+ mDisplayDeviceInfo.yDpi = 1.0f;
+ DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
+ mMockDisplayAdapter, /*isAnisotropyCorrectionEnabled=*/ true);
+ displayDevice.setProjectionLocked(mMockTransaction, ROTATION_90, new Rect(), new Rect());
+ assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(
+ LANDSCAPE_DOUBLE_HEIGHT);
+ }
+
+ @Test
public void testGetDisplaySurfaceDefaultSizeLocked_rotation90() {
DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
mMockDisplayAdapter);
@@ -111,8 +134,14 @@
private final DisplayDeviceInfo mDisplayDeviceInfo;
FakeDisplayDevice(DisplayDeviceInfo displayDeviceInfo, DisplayAdapter displayAdapter) {
+ this(displayDeviceInfo, displayAdapter, /*isAnisotropyCorrectionEnabled=*/ false);
+ }
+
+ FakeDisplayDevice(DisplayDeviceInfo displayDeviceInfo, DisplayAdapter displayAdapter,
+ boolean isAnisotropyCorrectionEnabled) {
super(displayAdapter, /* displayToken= */ null, /* uniqueId= */ "",
- InstrumentationRegistry.getInstrumentation().getContext());
+ InstrumentationRegistry.getInstrumentation().getContext(),
+ isAnisotropyCorrectionEnabled);
mDisplayDeviceInfo = displayDeviceInfo;
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
index 1c43418..549f0d7 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
@@ -106,8 +106,184 @@
}
@Test
+ public void testLetterbox() {
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+ /*isAnisotropyCorrectionEnabled=*/ false);
+ mDisplayDeviceInfo.xDpi = 0.5f;
+ mDisplayDeviceInfo.yDpi = 1.0f;
+
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+ var originalDisplayInfo = mLogicalDisplay.getDisplayInfoLocked();
+ assertEquals(DISPLAY_WIDTH, originalDisplayInfo.logicalWidth);
+ assertEquals(DISPLAY_HEIGHT, originalDisplayInfo.logicalHeight);
+
+ SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+ assertEquals(new Point(0, 0), mLogicalDisplay.getDisplayPosition());
+
+ /*
+ * Content is too wide, should become letterboxed
+ * ______DISPLAY_WIDTH________
+ * | |
+ * |________________________|
+ * | |
+ * | CONTENT |
+ * | |
+ * |________________________|
+ * | |
+ * |________________________|
+ */
+ // Make a wide application content, by reducing its height.
+ DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = DISPLAY_WIDTH;
+ displayInfo.logicalHeight = DISPLAY_HEIGHT / 2;
+ mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+ assertEquals(new Point(0, DISPLAY_HEIGHT / 4), mLogicalDisplay.getDisplayPosition());
+ }
+
+ @Test
+ public void testNoLetterbox_anisotropyCorrection() {
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+ /*isAnisotropyCorrectionEnabled=*/ true);
+
+ // In case of Anisotropy of pixels, then the content should be rescaled so it would adjust
+ // to using the whole screen. This is because display will rescale it back to fill the
+ // screen (in case the display menu setting is set to stretch the pixels across the display)
+ mDisplayDeviceInfo.xDpi = 0.5f;
+ mDisplayDeviceInfo.yDpi = 1.0f;
+
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+ var originalDisplayInfo = mLogicalDisplay.getDisplayInfoLocked();
+ // Content width re-scaled
+ assertEquals(DISPLAY_WIDTH * 2, originalDisplayInfo.logicalWidth);
+ assertEquals(DISPLAY_HEIGHT, originalDisplayInfo.logicalHeight);
+
+ SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+ // Applications need to think that they are shown on a display with square pixels.
+ // as applications can be displayed on multiple displays simultaneously (mirrored).
+ // Content is too wide, should have become letterboxed - but it won't because of anisotropy
+ // correction
+ assertEquals(new Point(0, 0), mLogicalDisplay.getDisplayPosition());
+ }
+
+ @Test
+ public void testLetterbox_anisotropyCorrectionYDpi() {
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+ /*isAnisotropyCorrectionEnabled=*/ true);
+
+ DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = DISPLAY_WIDTH;
+ displayInfo.logicalHeight = DISPLAY_HEIGHT / 2;
+ mDisplayDeviceInfo.xDpi = 1.0f;
+ mDisplayDeviceInfo.yDpi = 0.5f;
+ mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+
+ SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+ assertEquals(new Point(0, 75), mLogicalDisplay.getDisplayPosition());
+ }
+
+ @Test
+ public void testPillarbox() {
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+ /*isAnisotropyCorrectionEnabled=*/ false);
+ mDisplayDeviceInfo.xDpi = 0.5f;
+ mDisplayDeviceInfo.yDpi = 1.0f;
+
+ DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.rotation = Surface.ROTATION_90;
+ displayInfo.logicalWidth = DISPLAY_WIDTH;
+ displayInfo.logicalHeight = DISPLAY_HEIGHT;
+ mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
+ mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+
+ var updatedDisplayInfo = mLogicalDisplay.getDisplayInfoLocked();
+ assertEquals(Surface.ROTATION_90, updatedDisplayInfo.rotation);
+ assertEquals(DISPLAY_WIDTH, updatedDisplayInfo.logicalWidth);
+ assertEquals(DISPLAY_HEIGHT, updatedDisplayInfo.logicalHeight);
+
+ /*
+ * Content is too tall, should become pillarboxed
+ * ______DISPLAY_WIDTH________
+ * | | | |
+ * | | | |
+ * | | | |
+ * | | CONTENT | |
+ * | | | |
+ * | | | |
+ * |____|________________|____|
+ */
+
+ SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+ assertEquals(new Point(75, 0), mLogicalDisplay.getDisplayPosition());
+ }
+
+ @Test
+ public void testPillarbox_anisotropyCorrection() {
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+ /*isAnisotropyCorrectionEnabled=*/ true);
+
+ DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = DISPLAY_WIDTH;
+ displayInfo.logicalHeight = DISPLAY_HEIGHT;
+ displayInfo.rotation = Surface.ROTATION_90;
+ mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
+ // In case of Anisotropy of pixels, then the content should be rescaled so it would adjust
+ // to using the whole screen. This is because display will rescale it back to fill the
+ // screen (in case the display menu setting is set to stretch the pixels across the display)
+ mDisplayDeviceInfo.xDpi = 0.5f;
+ mDisplayDeviceInfo.yDpi = 1.0f;
+ mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+
+ SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+ // Applications need to think that they are shown on a display with square pixels.
+ // as applications can be displayed on multiple displays simultaneously (mirrored).
+ // Content is a bit wider than in #testPillarbox, due to content added stretching
+ assertEquals(new Point(50, 0), mLogicalDisplay.getDisplayPosition());
+ }
+
+ @Test
+ public void testNoPillarbox_anisotropyCorrectionYDpi() {
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+ /*isAnisotropyCorrectionEnabled=*/ true);
+
+ // In case of Anisotropy of pixels, then the content should be rescaled so it would adjust
+ // to using the whole screen. This is because display will rescale it back to fill the
+ // screen (in case the display menu setting is set to stretch the pixels across the display)
+ mDisplayDeviceInfo.xDpi = 1.0f;
+ mDisplayDeviceInfo.yDpi = 0.5f;
+
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+ var originalDisplayInfo = mLogicalDisplay.getDisplayInfoLocked();
+ // Content width re-scaled
+ assertEquals(DISPLAY_WIDTH, originalDisplayInfo.logicalWidth);
+ assertEquals(DISPLAY_HEIGHT * 2, originalDisplayInfo.logicalHeight);
+
+ SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+ // Applications need to think that they are shown on a display with square pixels.
+ // as applications can be displayed on multiple displays simultaneously (mirrored).
+ // Content is too tall, should have occupy the whole screen - but it won't because of
+ // anisotropy correction
+ assertEquals(new Point(0, 0), mLogicalDisplay.getDisplayPosition());
+ }
+
+ @Test
public void testGetDisplayPosition() {
- Point expectedPosition = new Point();
+ Point expectedPosition = new Point(0, 0);
SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index 64076e6..3eced7f 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -1631,12 +1631,25 @@
director.start(sensorManager);
director.injectSupportedModesByDisplay(supportedModesByDisplay);
- setPeakRefreshRate(Float.POSITIVE_INFINITY);
+ // Disable Smooth Display
+ setPeakRefreshRate(RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
Vote vote1 = director.getVote(DISPLAY_ID,
Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
Vote vote2 = director.getVote(DISPLAY_ID_2,
Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+ assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0,
+ /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+ assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0,
+ /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+
+ // Enable Smooth Display
+ setPeakRefreshRate(Float.POSITIVE_INFINITY);
+
+ vote1 = director.getVote(DISPLAY_ID,
+ Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+ vote2 = director.getVote(DISPLAY_ID_2,
+ Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0, /* frameRateHigh= */ 130);
assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0, /* frameRateHigh= */ 140);
}
@@ -1654,10 +1667,18 @@
SensorManager sensorManager = createMockSensorManager(lightSensor);
director.start(sensorManager);
- setPeakRefreshRate(peakRefreshRate);
+ // Disable Smooth Display
+ setPeakRefreshRate(RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0,
+ /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+
+ // Enable Smooth Display
+ setPeakRefreshRate(peakRefreshRate);
+
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+ assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0,
/* frameRateHigh= */ peakRefreshRate);
}
@@ -1759,11 +1780,23 @@
director.start(sensorManager);
director.injectSupportedModesByDisplay(supportedModesByDisplay);
- setMinRefreshRate(Float.POSITIVE_INFINITY);
+ // Disable Force Peak Refresh Rate
+ setMinRefreshRate(0);
Vote vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
Vote vote2 = director.getVote(DISPLAY_ID_2,
Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+ assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0,
+ /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+ assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0,
+ /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+
+ // Enable Force Peak Refresh Rate
+ setMinRefreshRate(Float.POSITIVE_INFINITY);
+
+ vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+ vote2 = director.getVote(DISPLAY_ID_2,
+ Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 130,
/* frameRateHigh= */ Float.POSITIVE_INFINITY);
assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 140,
@@ -1783,9 +1816,17 @@
SensorManager sensorManager = createMockSensorManager(lightSensor);
director.start(sensorManager);
- setMinRefreshRate(minRefreshRate);
+ // Disable Force Peak Refresh Rate
+ setMinRefreshRate(0);
Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+ assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0,
+ /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+
+ // Enable Force Peak Refresh Rate
+ setMinRefreshRate(minRefreshRate);
+
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ minRefreshRate,
/* frameRateHigh= */ Float.POSITIVE_INFINITY);
}
@@ -1829,6 +1870,58 @@
}
@Test
+ public void testPeakAndMinRefreshRate_FlagEnabled_DisplayWithOneMode() {
+ when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
+ .thenReturn(true);
+ DisplayModeDirector director =
+ new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags);
+ director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+ Display.Mode[] modes1 = new Display.Mode[] {
+ new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 60),
+ new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 130),
+ };
+ Display.Mode[] modes2 = new Display.Mode[] {
+ new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 60),
+ };
+ SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>();
+ supportedModesByDisplay.put(DISPLAY_ID, modes1);
+ supportedModesByDisplay.put(DISPLAY_ID_2, modes2);
+
+ Sensor lightSensor = createLightSensor();
+ SensorManager sensorManager = createMockSensorManager(lightSensor);
+ director.start(sensorManager);
+ director.injectSupportedModesByDisplay(supportedModesByDisplay);
+
+ // Disable Force Peak Refresh Rate and Smooth Display
+ setMinRefreshRate(0);
+ setPeakRefreshRate(RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+
+ // Even though the highest refresh rate of the second display == the current min refresh
+ // rate == 60, Force Peak Refresh Rate should remain disabled
+ Vote vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+ Vote vote2 = director.getVote(DISPLAY_ID_2,
+ Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+ assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0,
+ /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+ assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0,
+ /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+
+ // Even though the highest refresh rate of the second display == the current peak refresh
+ // rate == 60, Smooth Display should remain disabled
+ vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+ vote2 = director.getVote(DISPLAY_ID_2,
+ Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+ assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0,
+ /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+ assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0,
+ /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+ }
+
+ @Test
public void testSensorRegistration() {
// First, configure brightness zones or DMD won't register for sensor data.
final FakeDeviceConfig config = mInjector.getDeviceConfig();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
index caa0864..a8b792e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
@@ -213,7 +213,7 @@
any(), any(), any(),
any(), any(),
any(), any(),
- any(),
+ any(), any(),
anyLong(), anyLong());
final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid));
@@ -277,7 +277,7 @@
null, null,
null,
null, null, null,
- null, null,
+ null, null, null,
0, 0);
// Sleep until timeout should have triggered
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
index bd20ae2..ce281da 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
@@ -56,6 +56,7 @@
import org.junit.Rule;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.io.File;
@@ -160,6 +161,7 @@
realAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
realAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal());
+ realAms.mOomAdjuster.mCachedAppOptimizer = Mockito.mock(CachedAppOptimizer.class);
realAms.mOomAdjuster = spy(realAms.mOomAdjuster);
ExtendedMockito.doNothing().when(() -> ProcessList.setOomAdj(anyInt(), anyInt(), anyInt()));
realAms.mPackageManagerInt = mPackageManagerInt;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index a2756ff..0ba74c6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -1413,6 +1413,9 @@
final BroadcastRecord userPresentRecord2 = makeBroadcastRecord(userPresent);
mImpl.enqueueBroadcastLocked(userPresentRecord1);
+ // Wait for a few ms before sending another broadcast to allow comparing the
+ // enqueue timestamps of these broadcasts.
+ SystemClock.sleep(5);
mImpl.enqueueBroadcastLocked(userPresentRecord2);
final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN,
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 66ab807..1172685 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -86,7 +86,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.mockito.ArgumentMatcher;
import org.mockito.InOrder;
@@ -2335,8 +2334,8 @@
.isGreaterThan(getReceiverScheduledTime(prioritizedRecord, receiverBlue));
}
- @Ignore
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_DEFER_OUTGOING_BCASTS)
public void testDeferOutgoingBroadcasts() throws Exception {
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
setProcessFreezable(callerApp, true /* pendingFreeze */, false /* frozen */);
@@ -2350,6 +2349,8 @@
makeRegisteredReceiver(receiverGreenApp),
makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW))));
+ // Verify that we invoke the call to freeze the caller app.
+ verify(mAms.mOomAdjuster.mCachedAppOptimizer).freezeAppAsyncImmediateLSP(callerApp);
waitForIdle();
verifyScheduleRegisteredReceiver(never(), receiverGreenApp, timeTick);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
index fcf761f..67be93b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
@@ -215,7 +215,7 @@
any(), any(), any(),
any(), any(),
any(), any(),
- any(),
+ any(), any(),
anyLong(), anyLong());
final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid));
r.setPid(myPid());
@@ -263,7 +263,7 @@
null, null,
null,
null, null, null,
- null, null,
+ null, null, null,
0, 0);
return app;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS b/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS
index daa0211..8337fd2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS
@@ -1,3 +1 @@
-ancr@google.com
-harshitmahajan@google.com
-robertogil@google.com
+include /services/core/java/com/android/server/crashrecovery/OWNERS
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index e168596..16d05b1 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -214,7 +214,7 @@
.thenReturn(mA11yWindowInfos.get(0));
when(mMockA11yWindowManager.findA11yWindowInfoByIdLocked(PIP_WINDOWID))
.thenReturn(mA11yWindowInfos.get(1));
- when(mMockA11yWindowManager.getDisplayIdByUserIdAndWindowIdLocked(USER_ID,
+ when(mMockA11yWindowManager.getDisplayIdByUserIdAndWindowId(USER_ID,
WINDOWID_ONSECONDDISPLAY)).thenReturn(SECONDARY_DISPLAY_ID);
when(mMockA11yWindowManager.getWindowListLocked(SECONDARY_DISPLAY_ID))
.thenReturn(mA11yWindowInfosOnSecondDisplay);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
index dc26e6e..f6dc2f0 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
@@ -18,21 +18,23 @@
import static com.android.server.accessibility.AbstractAccessibilityServiceConnection.DISPLAY_TYPE_DEFAULT;
import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.DisplayIdMatcher.displayId;
+import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowIdMatcher.windowId;
import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowChangesMatcher.a11yWindowChanges;
-import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowIdMatcher.a11yWindowId;
+import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.EventWindowIdMatcher.eventWindowId;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
@@ -350,6 +352,88 @@
}
@Test
+ public void onWindowsChanged_shouldNotReportNonTouchableWindow() {
+ final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ when(window.isTouchable()).thenReturn(false);
+ final int windowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, window.getWindowInfo().token);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, not(hasItem(windowId(windowId))));
+ }
+
+ @Test
+ public void onWindowsChanged_shouldReportFocusedNonTouchableWindow() {
+ final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX);
+ when(window.isTouchable()).thenReturn(false);
+ final int windowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, window.getWindowInfo().token);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasItem(windowId(windowId)));
+ }
+
+ @Test
+ public void onWindowsChanged_trustedFocusedNonTouchableWindow_shouldNotHideWindowsBelow() {
+ // Make the focused trusted un-touchable window fullscreen.
+ final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX);
+ setRegionForMockAccessibilityWindow(window, new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+ when(window.isTouchable()).thenReturn(false);
+ when(window.isTrustedOverlay()).thenReturn(true);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(NUM_OF_WINDOWS));
+ }
+
+ @Test
+ public void onWindowsChanged_accessibilityOverlay_shouldNotHideWindowsBelow() {
+ // Make the a11y overlay window fullscreen.
+ final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(window, new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+ when(window.getType()).thenReturn(WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(NUM_OF_WINDOWS));
+ }
+
+ @Test
+ public void onWindowsChanged_shouldReportFocusedWindowEvenIfOccluded() {
+ // Make the front window fullscreen.
+ final AccessibilityWindow frontWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(frontWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+ final int frontWindowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, frontWindow.getWindowInfo().token);
+
+ final AccessibilityWindow focusedWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX);
+ final int focusedWindowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, focusedWindow.getWindowInfo().token);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(2));
+ assertThat(a11yWindows.get(0), windowId(frontWindowId));
+ assertThat(a11yWindows.get(1), windowId(focusedWindowId));
+ }
+
+ @Test
public void onWindowsChangedAndForceSend_shouldUpdateWindows() {
assertNotEquals("new title",
toString(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)
@@ -631,11 +715,11 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(currentActiveWindowId),
+ eventWindowId(currentActiveWindowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
assertThat(captor.getAllValues().get(1),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(eventWindowId),
+ eventWindowId(eventWindowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
}
@@ -661,7 +745,7 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(eventWindowId),
+ eventWindowId(eventWindowId),
a11yWindowChanges(
AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
}
@@ -710,12 +794,12 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(initialDisplayId),
- a11yWindowId(initialWindowId),
+ eventWindowId(initialWindowId),
a11yWindowChanges(
AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
assertThat(captor.getAllValues().get(1),
allOf(displayId(eventDisplayId),
- a11yWindowId(eventWindowId),
+ eventWindowId(eventWindowId),
a11yWindowChanges(
AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
}
@@ -771,11 +855,11 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(eventWindowId),
+ eventWindowId(eventWindowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
assertThat(captor.getAllValues().get(1),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(currentActiveWindowId),
+ eventWindowId(currentActiveWindowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
}
@@ -979,7 +1063,7 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(windowId),
+ eventWindowId(windowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_REMOVED)));
}
@@ -1001,7 +1085,7 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(windowId),
+ eventWindowId(windowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ADDED)));
}
@@ -1019,7 +1103,7 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(windowId),
+ eventWindowId(windowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_TITLE)));
}
@@ -1173,8 +1257,6 @@
private AccessibilityWindow createMockAccessibilityWindow(IWindow windowToken, int displayId) {
final WindowInfo windowInfo = WindowInfo.obtain();
- // TODO(b/325341171): add tests with various kinds of windows such as
- // changing window types, touchable or not, trusted or not, etc.
windowInfo.type = WindowManager.LayoutParams.TYPE_APPLICATION;
windowInfo.token = windowToken.asBinder();
@@ -1235,16 +1317,16 @@
}
}
- static class WindowIdMatcher extends TypeSafeMatcher<AccessibilityEvent> {
+ static class EventWindowIdMatcher extends TypeSafeMatcher<AccessibilityEvent> {
private int mWindowId;
- WindowIdMatcher(int windowId) {
+ EventWindowIdMatcher(int windowId) {
super();
mWindowId = windowId;
}
- static WindowIdMatcher a11yWindowId(int windowId) {
- return new WindowIdMatcher(windowId);
+ static EventWindowIdMatcher eventWindowId(int windowId) {
+ return new EventWindowIdMatcher(windowId);
}
@Override
@@ -1280,4 +1362,27 @@
description.appendText("Matching to window changes " + mWindowChanges);
}
}
+
+ static class WindowIdMatcher extends TypeSafeMatcher<AccessibilityWindowInfo> {
+ private final int mWindowId;
+
+ WindowIdMatcher(int windowId) {
+ super();
+ mWindowId = windowId;
+ }
+
+ static WindowIdMatcher windowId(int windowId) {
+ return new WindowIdMatcher(windowId);
+ }
+
+ @Override
+ protected boolean matchesSafely(AccessibilityWindowInfo window) {
+ return window.getId() == mWindowId;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Matching to windowId " + mWindowId);
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
index ef15f60..36b163e 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
@@ -173,6 +173,25 @@
}
@Test
+ public void testGetLoggableChanges() throws Exception {
+ final long disabledChangeId = 1234L;
+ final long enabledLatestChangeId = 2345L;
+ final long enabledOlderChangeId = 3456L;
+ CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+ // Disabled changes should not be logged.
+ .addDisabledChangeWithId(disabledChangeId)
+ // A change targeting the latest sdk should be logged.
+ .addEnableSinceSdkChangeWithId(3, enabledLatestChangeId)
+ // A change targeting an old sdk should not be logged.
+ .addEnableSinceSdkChangeWithId(1, enabledOlderChangeId)
+ .build();
+
+ assertThat(compatConfig.getLoggableChanges(
+ ApplicationInfoBuilder.create().withTargetSdk(3).build()))
+ .asList().containsExactly(enabledLatestChangeId);
+ }
+
+ @Test
public void testPackageOverrideEnabled() throws Exception {
CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
.addDisabledChangeWithId(1234L)
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
index 1dd64ff..5582e13 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
@@ -145,6 +145,7 @@
@SmallTest
@Test
+ @Ignore("b/277916462")
public void testCompMigrationUnAffiliated_skipped() throws Exception {
prepareAdmin1AsDo();
prepareAdminAnotherPackageAsPo(COPE_PROFILE_USER_ID);
@@ -216,6 +217,7 @@
@SmallTest
@Test
+ @Ignore("b/277916462")
public void testCompMigration_keepSuspendedAppsWhenDpcIsRPlus() throws Exception {
prepareAdmin1AsDo();
prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.R);
@@ -249,6 +251,7 @@
@SmallTest
@Test
+ @Ignore("b/277916462")
public void testCompMigration_unsuspendAppsWhenDpcNotRPlus() throws Exception {
prepareAdmin1AsDo();
prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.Q);
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 03f2749..f29d215 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -6223,6 +6223,52 @@
}
@Test
+ public void testSensitiveAdjustmentsLogged() 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);
+
+ // Set up notifications that will be adjusted
+ final NotificationRecord r1 = spy(generateNotificationRecord(
+ mTestNotificationChannel, 1, null, true));
+ when(r1.getLifespanMs(anyLong())).thenReturn(1);
+
+ r1.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
+ mService.addEnqueuedNotification(r1);
+
+ // Test an adjustment for an enqueued notification
+ Bundle signals = new Bundle();
+ signals.putBoolean(Adjustment.KEY_SENSITIVE_CONTENT, true);
+ Adjustment adjustment1 = new Adjustment(
+ r1.getSbn().getPackageName(), r1.getKey(), signals, "",
+ r1.getUser().getIdentifier());
+ mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment1);
+ assertTrue(mService.checkLastSensitiveLog(false, true, 1));
+
+ // Set up notifications that will be adjusted
+ final NotificationRecord r2 = spy(generateNotificationRecord(
+ mTestNotificationChannel, 1, null, true));
+ when(r2.getLifespanMs(anyLong())).thenReturn(2);
+
+ r2.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
+ mService.addNotification(r2);
+ Adjustment adjustment2 = new Adjustment(
+ r2.getSbn().getPackageName(), r2.getKey(), signals, "",
+ r2.getUser().getIdentifier());
+ mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment2);
+ assertTrue(mService.checkLastSensitiveLog(true, true, 2));
+
+ signals.putBoolean(Adjustment.KEY_SENSITIVE_CONTENT, false);
+ Adjustment adjustment3 = new Adjustment(
+ r2.getSbn().getPackageName(), r2.getKey(), signals, "",
+ r2.getUser().getIdentifier());
+ mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment3);
+ assertTrue(mService.checkLastSensitiveLog(true, false, 2));
+ }
+
+ @Test
public void testAdjustmentToImportanceNone_cancelsNotification() throws Exception {
NotificationManagerService.WorkerHandler handler = mock(
NotificationManagerService.WorkerHandler.class);
@@ -12008,7 +12054,7 @@
// style + self managed call - bypasses block
when(mTelecomManager.isInSelfManagedCall(
- r.getSbn().getPackageName(), true)).thenReturn(true);
+ r.getSbn().getPackageName(), UserHandle.ALL)).thenReturn(true);
assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue();
@@ -12091,7 +12137,7 @@
// style + self managed call - bypasses block
mService.clearNotifications();
reset(mUsageStats);
- when(mTelecomManager.isInSelfManagedCall(r.getSbn().getPackageName(), true))
+ when(mTelecomManager.isInSelfManagedCall(r.getSbn().getPackageName(), UserHandle.ALL))
.thenReturn(true);
mService.addEnqueuedNotification(r);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
index 6976ec3..07d25df 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
@@ -45,6 +45,13 @@
ComponentPermissionChecker permissionChecker;
+ private static class SensitiveLog {
+ public boolean hasPosted;
+ public boolean hasSensitiveContent;
+ public long lifetime;
+ }
+ public SensitiveLog lastSensitiveLog = null;
+
TestableNotificationManagerService(Context context, NotificationRecordLogger logger,
InstanceIdSequence notificationInstanceIdSequence) {
super(context, logger, notificationInstanceIdSequence);
@@ -167,6 +174,15 @@
return permissionChecker.check(permission, uid, owningUid, exported);
}
+ @Override
+ protected void logSensitiveAdjustmentReceived(boolean hasPosted, boolean hasSensitiveContent,
+ int lifetimeMs) {
+ lastSensitiveLog = new SensitiveLog();
+ lastSensitiveLog.hasPosted = hasPosted;
+ lastSensitiveLog.hasSensitiveContent = hasSensitiveContent;
+ lastSensitiveLog.lifetime = lifetimeMs;
+ }
+
public class StrongAuthTrackerFake extends NotificationManagerService.StrongAuthTracker {
private int mGetStrongAuthForUserReturnValue = 0;
StrongAuthTrackerFake(Context context) {
@@ -183,6 +199,15 @@
}
}
+ public boolean checkLastSensitiveLog(boolean hasPosted, boolean hasSensitive, int lifetime) {
+ if (lastSensitiveLog == null) {
+ return false;
+ }
+ return hasPosted == lastSensitiveLog.hasPosted
+ && hasSensitive == lastSensitiveLog.hasSensitiveContent
+ && lifetime == lastSensitiveLog.lifetime;
+ }
+
public interface ComponentPermissionChecker {
int check(String permission, int uid, int owningUid, boolean exported);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
index 99d354a..b11f9b2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
@@ -70,6 +70,7 @@
@Before
public void before() {
+ doReturn(true).when(mDisplayContent).getLastHasContent();
mockTransitionsController(/* enabled= */ true);
mockRemoteDisplayChangeController();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index 32b3558..da437c4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -33,6 +33,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.ActivityRecord.State.PAUSED;
+import static com.android.server.wm.ActivityRecord.State.STOPPING;
import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -164,13 +165,12 @@
ActivityRecord recentsActivity = recentsStack.getTopNonFinishingActivity();
// The activity is started in background so it should be invisible and will be stopped.
assertThat(recentsActivity).isNotNull();
- assertThat(mSupervisor.mStoppingActivities).contains(recentsActivity);
+ assertThat(recentsActivity.getState()).isEqualTo(STOPPING);
assertFalse(recentsActivity.isVisibleRequested());
// Assume it is stopped to test next use case.
recentsActivity.activityStopped(null /* newIcicle */, null /* newPersistentState */,
null /* description */);
- mSupervisor.mStoppingActivities.remove(recentsActivity);
spyOn(recentsActivity);
// Start when the recents activity exists. It should ensure the configuration.
@@ -178,7 +178,6 @@
null /* recentsAnimationRunner */);
verify(recentsActivity).ensureActivityConfiguration(eq(true) /* ignoreVisibility */);
- assertThat(mSupervisor.mStoppingActivities).contains(recentsActivity);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 961fdfb..10cfb5f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -315,6 +315,23 @@
}
@Test
+ public void testUserLeaving() {
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final Task task = activity.getTask();
+ mSupervisor.mUserLeaving = true;
+ activity.setState(ActivityRecord.State.RESUMED, "test");
+ task.sleepIfPossible(false /* shuttingDown */);
+ verify(task).startPausing(eq(true) /* userLeaving */, anyBoolean(), any(), any());
+
+ clearInvocations(task);
+ activity.setState(ActivityRecord.State.RESUMED, "test");
+ task.setPausingActivity(null);
+ doReturn(false).when(task).canBeResumed(any());
+ task.pauseActivityIfNeeded(null /* resuming */, "test");
+ verify(task).startPausing(eq(true) /* userLeaving */, anyBoolean(), any(), any());
+ }
+
+ @Test
public void testSwitchUser() {
final Task rootTask = createTask(mDisplayContent);
final Task childTask = createTaskInRootTask(rootTask, 0 /* userId */);
@@ -1823,6 +1840,66 @@
}
@Test
+ public void testAssignChildLayers_boostedDecorSurfacePlacement() {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
+ final ActivityRecord unembeddedActivity = task.getTopMostActivity();
+
+ final TaskFragment fragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final TaskFragment fragment2 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final SurfaceControl.Transaction t = task.getSyncTransaction();
+ final SurfaceControl.Transaction clientTransaction = mock(SurfaceControl.Transaction.class);
+
+ doNothing().when(task).sendTaskFragmentParentInfoChangedIfNeeded();
+ spyOn(unembeddedActivity);
+ spyOn(fragment1);
+ spyOn(fragment2);
+
+ doReturn(true).when(unembeddedActivity).isUid(task.effectiveUid);
+ doReturn(true).when(fragment1).isAllowedToBeEmbeddedInTrustedMode();
+ doReturn(false).when(fragment2).isAllowedToBeEmbeddedInTrustedMode();
+ doReturn(true).when(fragment1).isVisible();
+
+ task.moveOrCreateDecorSurfaceFor(fragment1);
+
+ clearInvocations(t);
+ clearInvocations(unembeddedActivity);
+ clearInvocations(fragment1);
+ clearInvocations(fragment2);
+
+ // The decor surface should be placed above all the windows when boosted and the cover
+ // surface should show.
+ task.setDecorSurfaceBoosted(fragment1, true /* isBoosted */, clientTransaction);
+
+ verify(unembeddedActivity).assignLayer(t, 0);
+ verify(fragment1).assignLayer(t, 1);
+ verify(fragment2).assignLayer(t, 2);
+ verify(t).setLayer(task.mDecorSurfaceContainer.mContainerSurface, 3);
+
+ verify(t).setVisibility(task.mDecorSurfaceContainer.mContainerSurface, true);
+ verify(t).merge(clientTransaction);
+
+ clearInvocations(t);
+ clearInvocations(unembeddedActivity);
+ clearInvocations(fragment1);
+ clearInvocations(fragment2);
+
+ // The decor surface should be placed just above the owner TaskFragment and the cover
+ // surface should hide.
+ task.moveOrCreateDecorSurfaceFor(fragment1);
+ task.setDecorSurfaceBoosted(fragment1, false /* isBoosted */, clientTransaction);
+
+ verify(unembeddedActivity).assignLayer(t, 0);
+ verify(fragment1).assignLayer(t, 1);
+ verify(t).setLayer(task.mDecorSurfaceContainer.mContainerSurface, 2);
+ verify(fragment2).assignLayer(t, 3);
+
+ verify(t).setVisibility(task.mDecorSurfaceContainer.mContainerSurface, true);
+ verify(t).merge(clientTransaction);
+
+ }
+
+ @Test
public void testMoveTaskFragmentsToBottomIfNeeded() {
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index dc504ca..a35a35a 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -153,7 +153,8 @@
= SystemProperties.getBoolean("persist.debug.time_correction", true);
private static final boolean USE_DEDICATED_HANDLER_THREAD =
- SystemProperties.getBoolean("persist.debug.use_dedicated_handler_thread", false);
+ SystemProperties.getBoolean("persist.debug.use_dedicated_handler_thread",
+ Flags.useDedicatedHandlerThread());
static final boolean DEBUG = false; // Never submit with true
static final boolean DEBUG_RESPONSE_STATS = DEBUG || Log.isLoggable(TAG, Log.DEBUG);
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 048b1b2..ff4be55 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -2797,7 +2797,9 @@
/**
* Determines whether there are any ongoing {@link PhoneAccount#CAPABILITY_SELF_MANAGED}
- * calls for a given {@code packageName} and {@code userHandle}.
+ * calls for a given {@code packageName} and {@code userHandle}. If UserHandle.ALL or a user
+ * that isn't the calling user is passed in, the caller will need to have granted the ability
+ * to interact across users.
*
* @param packageName the package name of the app to check calls for.
* @param userHandle the user handle to check calls for.
@@ -2816,41 +2818,7 @@
if (service != null) {
try {
return service.isInSelfManagedCall(packageName, userHandle,
- mContext.getOpPackageName(), false);
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException isInSelfManagedCall: " + e);
- e.rethrowFromSystemServer();
- return false;
- }
- } else {
- throw new IllegalStateException("Telecom service is not present");
- }
- }
-
- /**
- * Determines whether there are any ongoing {@link PhoneAccount#CAPABILITY_SELF_MANAGED}
- * calls for a given {@code packageName} amongst all users, given that detectForAllUsers is true
- * and the caller has the ability to interact across users. If detectForAllUsers isn't enabled,
- * the calls will be checked against the caller.
- *
- * @param packageName the package name of the app to check calls for.
- * @param detectForAllUsers indicates if calls should be detected across all users.
- * @return {@code true} if there are ongoing calls, {@code false} otherwise.
- * @throws SecurityException if detectForAllUsers is true and the caller does not grant the
- * ability to interact across users.
- * @hide
- */
- @SystemApi
- @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
- @RequiresPermission(allOf = {Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
- Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
- public boolean isInSelfManagedCall(@NonNull String packageName,
- boolean detectForAllUsers) {
- ITelecomService service = getTelecomService();
- if (service != null) {
- try {
- return service.isInSelfManagedCall(packageName, null,
- mContext.getOpPackageName(), detectForAllUsers);
+ mContext.getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "RemoteException isInSelfManagedCall: " + e);
e.rethrowFromSystemServer();
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 302a472..112471b 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -401,7 +401,7 @@
* @see TelecomServiceImpl#isInSelfManagedCall
*/
boolean isInSelfManagedCall(String packageName, in UserHandle userHandle,
- String callingPackage, boolean detectForAllUsers);
+ String callingPackage);
/**
* @see TelecomServiceImpl#addCall
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index d3a50bb..df32fbd 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -3315,6 +3315,18 @@
"support_no_reply_timer_for_cfnry_bool";
/**
+ * No reply time value to be sent to network for call forwarding on no reply
+ * (CFNRy 3GPP TS 24.082 version 17.0 section 3).
+ * Controls time in seconds for the no reply condition on in the call forwarding
+ * settings UI.
+ * This is available when {@link #KEY_SUPPORT_NO_REPLY_TIMER_FOR_CFNRY_BOOL} is true.
+ *
+ * @hide
+ */
+ public static final String KEY_NO_REPLY_TIMER_FOR_CFNRY_SEC_INT =
+ "no_reply_timer_for_cfnry_sec_int";
+
+ /**
* List of the FAC (feature access codes) to dial as a normal call.
* @hide
*/
@@ -10666,6 +10678,7 @@
sDefaults.putBoolean(KEY_VT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_RTT_CALL_BOOL, true);
sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false);
sDefaults.putBoolean(KEY_SUPPORT_NO_REPLY_TIMER_FOR_CFNRY_BOOL, true);
+ sDefaults.putInt(KEY_NO_REPLY_TIMER_FOR_CFNRY_SEC_INT, 20);
sDefaults.putStringArray(KEY_FEATURE_ACCESS_CODES_STRING_ARRAY, null);
sDefaults.putBoolean(KEY_IDENTIFY_HIGH_DEFINITION_CALLS_IN_CALL_LOG_BOOL, false);
sDefaults.putBoolean(KEY_SHOW_PRECISE_FAILED_CAUSE_BOOL, false);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 88acbab..a047b97 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -18852,7 +18852,7 @@
*/
@SystemApi
@FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
- @RequiresPermission(android.Manifest.permission.DUMP)
+ @RequiresPermission(android.Manifest.permission.READ_DROPBOX_DATA)
public void persistEmergencyCallDiagnosticData(@NonNull String dropboxTag,
@NonNull EmergencyCallDiagnosticData data) {
try {
diff --git a/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl b/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl
index e69b60b..c349599 100644
--- a/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl
+++ b/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl
@@ -26,5 +26,5 @@
{
void onQualifiedNetworkTypesChanged(int apnTypes, in int[] qualifiedNetworkTypes);
void onNetworkValidationRequested(int networkCapability, IIntegerConsumer callback);
- void onReconnectQualifedNetworkType(int apnTypes, int qualifiedNetworkType);
+ void onReconnectQualifiedNetworkType(int apnTypes, int qualifiedNetworkType);
}
diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java
index 7bfe04d..f775de6 100644
--- a/telephony/java/android/telephony/data/QualifiedNetworksService.java
+++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java
@@ -238,7 +238,7 @@
@AccessNetworkConstants.RadioAccessNetworkType int qualifiedNetworkType) {
if (mCallback != null) {
try {
- mCallback.onReconnectQualifedNetworkType(apnTypes, qualifiedNetworkType);
+ mCallback.onReconnectQualifiedNetworkType(apnTypes, qualifiedNetworkType);
} catch (RemoteException e) {
loge("Failed to call onReconnectQualifiedNetworkType. " + e);
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index 17f91eb..060015b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -40,9 +40,10 @@
constructor(
protected val flicker: LegacyFlickerTest,
protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
+ protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
) {
- protected val tapl: LauncherInstrumentation by lazy {
- LauncherInstrumentation().also { it.expectedRotationCheckEnabled = true }
+ init {
+ tapl.setExpectedRotationCheckEnabled(true)
}
private val logTag = this::class.java.simpleName
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt
deleted file mode 100644
index 8faf224..0000000
--- a/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.input.screenshot
-
-import androidx.test.platform.app.InstrumentationRegistry
-import platform.test.screenshot.GoldenImagePathManager
-import platform.test.screenshot.PathConfig
-
-/** A [GoldenImagePathManager] that should be used for all Input screenshot tests. */
-class InputGoldenImagePathManager(
- pathConfig: PathConfig,
- assetsPathRelativeToBuildRoot: String
-) :
- GoldenImagePathManager(
- appContext = InstrumentationRegistry.getInstrumentation().context,
- assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
- deviceLocalPath =
- InstrumentationRegistry.getInstrumentation()
- .targetContext
- .filesDir
- .absolutePath
- .toString() + "/input_screenshots",
- pathConfig = pathConfig,
- ) {
- override fun toString(): String {
- // This string is appended to all actual/expected screenshots on the device, so make sure
- // it is a static value.
- return "InputGoldenImagePathManager"
- }
-}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenPathManager.kt
similarity index 62%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt
copy to tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenPathManager.kt
index f5fba7f..9f14b136 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenPathManager.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,31 +14,28 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.screenshot.util
+package com.android.input.screenshot
import androidx.test.platform.app.InstrumentationRegistry
-import platform.test.screenshot.GoldenImagePathManager
+import platform.test.screenshot.GoldenPathManager
import platform.test.screenshot.PathConfig
-/** A [GoldenImagePathManager] that should be used for all Settings screenshot tests. */
-class SettingsGoldenImagePathManager(
- pathConfig: PathConfig,
- assetsPathRelativeToBuildRoot: String
-) :
- GoldenImagePathManager(
+/** A [GoldenPathManager] that should be used for all Input screenshot tests. */
+class InputGoldenPathManager(pathConfig: PathConfig, assetsPathRelativeToBuildRoot: String) :
+ GoldenPathManager(
appContext = InstrumentationRegistry.getInstrumentation().context,
assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
deviceLocalPath =
- InstrumentationRegistry.getInstrumentation()
- .targetContext
- .filesDir
- .absolutePath
- .toString() + "/settings_screenshots",
+ InstrumentationRegistry.getInstrumentation()
+ .targetContext
+ .filesDir
+ .absolutePath
+ .toString() + "/input_screenshots",
pathConfig = pathConfig,
) {
override fun toString(): String {
// This string is appended to all actual/expected screenshots on the device, so make sure
// it is a static value.
- return "SettingsGoldenImagePathManager"
+ return "InputGoldenPathManager"
}
}
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
index 75dab41..2f40896 100644
--- a/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
@@ -44,7 +44,7 @@
private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
private val screenshotRule =
ScreenshotTestRule(
- InputGoldenImagePathManager(
+ InputGoldenPathManager(
getEmulatedDevicePathConfig(emulationSpec),
assetsPathRelativeToBuildRoot
)
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 755636a..75284c7 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -43,13 +43,13 @@
import android.os.test.TestLooper;
import android.provider.DeviceConfig;
import android.util.AtomicFile;
-import android.util.LongArrayQueue;
import android.util.Xml;
+import android.utils.LongArrayQueue;
+import android.utils.XmlUtils;
import androidx.test.InstrumentationRegistry;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.PackageWatchdog.HealthCheckState;