diff options
230 files changed, 3366 insertions, 752 deletions
diff --git a/core/java/android/os/PerfettoTrackEventExtra.java b/core/java/android/os/PerfettoTrackEventExtra.java index 2848bcb8ad34..8a3a5be9c934 100644 --- a/core/java/android/os/PerfettoTrackEventExtra.java +++ b/core/java/android/os/PerfettoTrackEventExtra.java @@ -214,9 +214,6 @@ public final class PerfettoTrackEventExtra { * Initialize the builder for a new trace event. */ public Builder init(int traceType, PerfettoTrace.Category category) { - if (!category.isEnabled()) { - return this; - } mTraceType = traceType; mCategory = category; mEventName = ""; @@ -228,7 +225,7 @@ public final class PerfettoTrackEventExtra { mExtra.reset(); // Reset after on init in case the thread created builders without calling emit - return initInternal(this, null, true); + return initInternal(this, null, category.isEnabled()); } /** diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 89f66c010976..1210790668de 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9357,6 +9357,16 @@ public final class Settings { "accessibility_autoclick_panel_position"; /** + * Setting that specifies whether autoclick type reverts to left click after performing + * an action when {@link #ACCESSIBILITY_AUTOCLICK_ENABLED} is set. + * + * @see #ACCESSIBILITY_AUTOCLICK_ENABLED + * @hide + */ + public static final String ACCESSIBILITY_AUTOCLICK_REVERT_TO_LEFT_CLICK = + "accessibility_autoclick_revert_to_left_click"; + + /** * Whether or not larger size icons are used for the pointer of mouse/trackpad for * accessibility. * (0 = false, 1 = true) diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index e157da72196a..9d0773f0a606 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -2071,26 +2071,40 @@ public final class ViewRootImpl implements ViewParent, */ @VisibleForTesting public @ForceDarkType.ForceDarkTypeDef int determineForceDarkType() { - if (forceInvertColor()) { - // Force invert ignores all developer opt-outs. - // We also ignore dark theme, since the app developer can override the user's preference - // for dark mode in configuration.uiMode. Instead, we assume that both force invert and - // the system's dark theme are enabled. - if (getUiModeManager().getForceInvertState() == UiModeManager.FORCE_INVERT_TYPE_DARK) { - return ForceDarkType.FORCE_INVERT_COLOR_DARK; - } - } - - boolean useAutoDark = getNightMode() == Configuration.UI_MODE_NIGHT_YES; - if (useAutoDark) { - boolean forceDarkAllowedDefault = - SystemProperties.getBoolean(ThreadedRenderer.DEBUG_FORCE_DARK, false); - TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme); - useAutoDark = a.getBoolean(R.styleable.Theme_isLightTheme, true) - && a.getBoolean(R.styleable.Theme_forceDarkAllowed, forceDarkAllowedDefault); + TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme); + try { + if (forceInvertColor()) { + // Force invert ignores all developer opt-outs. + // We also ignore dark theme, since the app developer can override the user's + // preference for dark mode in configuration.uiMode. Instead, we assume that both + // force invert and the system's dark theme are enabled. + if (getUiModeManager().getForceInvertState() == + UiModeManager.FORCE_INVERT_TYPE_DARK) { + final boolean isLightTheme = + a.getBoolean(R.styleable.Theme_isLightTheme, false); + // TODO: b/372558459 - Also check the background ColorDrawable color lightness + // TODO: b/368725782 - Use hwui color area detection instead of / in + // addition to these heuristics. + if (isLightTheme) { + return ForceDarkType.FORCE_INVERT_COLOR_DARK; + } else { + return ForceDarkType.NONE; + } + } + } + + boolean useAutoDark = getNightMode() == Configuration.UI_MODE_NIGHT_YES; + if (useAutoDark) { + boolean forceDarkAllowedDefault = + SystemProperties.getBoolean(ThreadedRenderer.DEBUG_FORCE_DARK, false); + useAutoDark = a.getBoolean(R.styleable.Theme_isLightTheme, true) + && a.getBoolean(R.styleable.Theme_forceDarkAllowed, + forceDarkAllowedDefault); + } + return useAutoDark ? ForceDarkType.FORCE_DARK : ForceDarkType.NONE; + } finally { a.recycle(); } - return useAutoDark ? ForceDarkType.FORCE_DARK : ForceDarkType.NONE; } private void updateForceDarkMode() { diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index d267c9451d1a..e43fb48527a1 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -163,6 +163,9 @@ public final class AccessibilityManager { /** @hide */ public static final boolean AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT_DEFAULT = false; + /** @hide */ + public static final boolean AUTOCLICK_REVERT_TO_LEFT_CLICK_DEFAULT = true; + /** * Activity action: Launch UI to manage which accessibility service or feature is assigned * to the navigation bar Accessibility button. diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java index 696b7b8bb965..1b8f73a7edb8 100644 --- a/core/java/android/window/DesktopModeFlags.java +++ b/core/java/android/window/DesktopModeFlags.java @@ -126,6 +126,8 @@ public enum DesktopModeFlags { ENABLE_TILE_RESIZING(Flags::enableTileResizing, true), ENABLE_TOP_VISIBLE_ROOT_TASK_PER_USER_TRACKING(Flags::enableTopVisibleRootTaskPerUserTracking, true), + ENABLE_VISUAL_INDICATOR_IN_TRANSITION_BUGFIX( + Flags::enableVisualIndicatorInTransitionBugfix, false), ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, true), ENABLE_WINDOWING_EDGE_DRAG_RESIZE(Flags::enableWindowingEdgeDragResize, true), ENABLE_WINDOWING_SCALED_RESIZING(Flags::enableWindowingScaledResizing, true), diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index 63c55ad6a889..be4edc3b6ec5 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -29,6 +29,7 @@ import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_NONE; @@ -375,7 +376,8 @@ public final class TransitionInfo implements Parcelable { */ public boolean hasChangesOrSideEffects() { return !mChanges.isEmpty() || isKeyguardGoingAway() - || (mFlags & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0; + || (mFlags & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0 + || (mFlags & TRANSIT_FLAG_AOD_APPEARING) != 0; } /** diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index 09f458be9bfa..2f2a09a9fac3 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -864,4 +864,14 @@ flag { metadata { purpose: PURPOSE_BUGFIX } -}
\ No newline at end of file +} + +flag { + name: "enable_visual_indicator_in_transition_bugfix" + namespace: "lse_desktop_experience" + description: "Enables bugfix to move visual drop-zone indicator to transition root, so it can't be shown after the transition." + bug: "392826275" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java index b8503da2c09b..796ea8d1538f 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java +++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java @@ -65,7 +65,7 @@ public class CoreDocument implements Serializable { // We also keep a more fine-grained BUILD number, exposed as // ID_API_LEVEL = DOCUMENT_API_LEVEL + BUILD - static final float BUILD = 0.2f; + static final float BUILD = 0.3f; @NonNull ArrayList<Operation> mOperations = new ArrayList<>(); @@ -411,7 +411,7 @@ public class CoreDocument implements Serializable { @Override public void serialize(MapSerializer serializer) { serializer - .add("type", "CoreDocument") + .addType("CoreDocument") .add("width", mWidth) .add("height", mHeight) .add("operations", mOperations); diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operations.java b/core/java/com/android/internal/widget/remotecompose/core/Operations.java index 09ec40271f4d..9cbafab07a43 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java +++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java @@ -100,6 +100,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.managers import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleColumnLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleRowLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.ColumnLayout; +import com.android.internal.widget.remotecompose.core.operations.layout.managers.FitBoxLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.RowLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.StateLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.TextLayout; @@ -231,6 +232,7 @@ public class Operations { public static final int LAYOUT_ROOT = 200; public static final int LAYOUT_CONTENT = 201; public static final int LAYOUT_BOX = 202; + public static final int LAYOUT_FIT_BOX = 176; public static final int LAYOUT_ROW = 203; public static final int LAYOUT_COLLAPSIBLE_ROW = 230; public static final int LAYOUT_COLUMN = 204; @@ -391,6 +393,7 @@ public class Operations { map.put(LAYOUT_ROOT, RootLayoutComponent::read); map.put(LAYOUT_CONTENT, LayoutComponentContent::read); map.put(LAYOUT_BOX, BoxLayout::read); + map.put(LAYOUT_FIT_BOX, FitBoxLayout::read); map.put(LAYOUT_COLUMN, ColumnLayout::read); map.put(LAYOUT_COLLAPSIBLE_COLUMN, CollapsibleColumnLayout::read); map.put(LAYOUT_ROW, RowLayout::read); diff --git a/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java b/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java index f355676be63e..c27ee3284b9c 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java @@ -17,11 +17,13 @@ package com.android.internal.widget.remotecompose.core; import android.annotation.NonNull; +import com.android.internal.widget.remotecompose.core.serialize.Serializable; + /** * PaintOperation interface, used for operations aimed at painting (while any operation _can_ paint, * this make it a little more explicit) */ -public abstract class PaintOperation extends Operation { +public abstract class PaintOperation extends Operation implements Serializable { @Override public void apply(@NonNull RemoteContext context) { diff --git a/core/java/com/android/internal/widget/remotecompose/core/Platform.java b/core/java/com/android/internal/widget/remotecompose/core/Platform.java index 6073de662cdb..3fff86c38b2e 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/Platform.java +++ b/core/java/com/android/internal/widget/remotecompose/core/Platform.java @@ -47,6 +47,14 @@ public interface Platform { int getImageHeight(@NonNull Object image); /** + * Returns true if the platform-specific image object has format ALPHA_8 + * + * @param image platform-specific image object + * @return whether or not the platform-specific image object has format ALPHA_8 + */ + boolean isAlpha8Image(@NonNull Object image); + + /** * Converts a platform-specific path object into a platform-independent float buffer * * @param path @@ -110,6 +118,11 @@ public interface Platform { } @Override + public boolean isAlpha8Image(@NonNull Object image) { + throw new UnsupportedOperationException(); + } + + @Override public float[] pathToFloatArray(@NonNull Object path) { throw new UnsupportedOperationException(); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java index e75bd30b381d..c249adf5bd58 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java @@ -98,6 +98,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.managers import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleColumnLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleRowLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.ColumnLayout; +import com.android.internal.widget.remotecompose.core.operations.layout.managers.FitBoxLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.RowLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.StateLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.TextLayout; @@ -281,13 +282,7 @@ public class RemoteComposeBuffer { int dstRight, int dstBottom, @Nullable String contentDescription) { - int imageId = mRemoteComposeState.dataGetId(image); - if (imageId == -1) { - imageId = mRemoteComposeState.cacheData(image); - byte[] data = mPlatform.imageToByteArray(image); // todo: potential npe - BitmapData.apply( - mBuffer, imageId, imageWidth, imageHeight, data); // todo: potential npe - } + int imageId = storeBitmap(image); int contentDescriptionId = 0; if (contentDescription != null) { contentDescriptionId = addText(contentDescription); @@ -443,16 +438,7 @@ public class RemoteComposeBuffer { float right, float bottom, @Nullable String contentDescription) { - int imageId = mRemoteComposeState.dataGetId(image); - if (imageId == -1) { - imageId = mRemoteComposeState.cacheData(image); - byte[] data = mPlatform.imageToByteArray(image); // todo: potential npe - int imageWidth = mPlatform.getImageWidth(image); - int imageHeight = mPlatform.getImageHeight(image); - - BitmapData.apply( - mBuffer, imageId, imageWidth, imageHeight, data); // todo: potential npe - } + int imageId = storeBitmap(image); addDrawBitmap(imageId, left, top, right, bottom, contentDescription); } @@ -523,15 +509,7 @@ public class RemoteComposeBuffer { int scaleType, float scaleFactor, @Nullable String contentDescription) { - int imageId = mRemoteComposeState.dataGetId(image); - if (imageId == -1) { - imageId = mRemoteComposeState.cacheData(image); - byte[] data = mPlatform.imageToByteArray(image); // todo: potential npe - int imageWidth = mPlatform.getImageWidth(image); - int imageHeight = mPlatform.getImageHeight(image); - - BitmapData.apply(mBuffer, imageId, imageWidth, imageHeight, data); // todo: potential pe - } + int imageId = storeBitmap(image); int contentDescriptionId = 0; if (contentDescription != null) { contentDescriptionId = addText(contentDescription); @@ -559,16 +537,7 @@ public class RemoteComposeBuffer { * @return id of the image useful with */ public int addBitmap(@NonNull Object image) { - int imageId = mRemoteComposeState.dataGetId(image); - if (imageId == -1) { - imageId = mRemoteComposeState.cacheData(image); - byte[] data = mPlatform.imageToByteArray(image); // tODO: potential npe - int imageWidth = mPlatform.getImageWidth(image); - int imageHeight = mPlatform.getImageHeight(image); - - BitmapData.apply(mBuffer, imageId, imageWidth, imageHeight, data); - } - return imageId; + return storeBitmap(image); } /** @@ -578,18 +547,7 @@ public class RemoteComposeBuffer { * @return id of the image useful with */ public int addBitmap(@NonNull Object image, @NonNull String name) { - int imageId = mRemoteComposeState.dataGetId(image); - if (imageId == -1) { - imageId = mRemoteComposeState.cacheData(image); - byte[] data = mPlatform.imageToByteArray(image); // todo: potential npe - int imageWidth = mPlatform.getImageWidth(image); - int imageHeight = mPlatform.getImageHeight(image); - - BitmapData.apply(mBuffer, imageId, imageWidth, imageHeight, data); - setBitmapName(imageId, name); - } - - return imageId; + return storeBitmap(image); } /** @@ -1393,7 +1351,7 @@ public class RemoteComposeBuffer { * @return the id of the command representing long */ public int addLong(long value) { - int id = mRemoteComposeState.cacheData(value); + int id = mRemoteComposeState.nextId(); LongConstant.apply(mBuffer, id, value); return id; } @@ -1405,7 +1363,7 @@ public class RemoteComposeBuffer { * @return the id */ public int addBoolean(boolean value) { - int id = mRemoteComposeState.cacheData(value); + int id = mRemoteComposeState.nextId(); BooleanConstant.apply(mBuffer, id, value); return id; } @@ -1821,33 +1779,14 @@ public class RemoteComposeBuffer { } /** - * This defines the name of the color given the id. - * - * @param id of the color - * @param name Name of the color - */ - public void setColorName(int id, @NonNull String name) { - NamedVariable.apply(mBuffer, id, NamedVariable.COLOR_TYPE, name); - } - - /** - * This defines the name of the string given the id - * - * @param id of the string - * @param name name of the string - */ - public void setStringName(int id, @NonNull String name) { - NamedVariable.apply(mBuffer, id, NamedVariable.STRING_TYPE, name); - } - - /** - * This defines the name of the float given the id + * This defines the name of a type of given object * * @param id of the float * @param name name of the float + * @param type the type of variable NamedVariable.COLOR_TYPE, STRING_TYPE, etc */ - public void setFloatName(int id, String name) { - NamedVariable.apply(mBuffer, id, NamedVariable.FLOAT_TYPE, name); + public void setNamedVariable(int id, @NonNull String name, int type) { + NamedVariable.apply(mBuffer, id, type, name); } /** @@ -2139,6 +2078,19 @@ public class RemoteComposeBuffer { } /** + * Add a fitbox start tag + * + * @param componentId component id + * @param animationId animation id + * @param horizontal horizontal alignment + * @param vertical vertical alignment + */ + public void addFitBoxStart(int componentId, int animationId, int horizontal, int vertical) { + mLastComponentId = getComponentId(componentId); + FitBoxLayout.apply(mBuffer, mLastComponentId, animationId, horizontal, vertical); + } + + /** * Add a row start tag * * @param componentId component id @@ -2439,4 +2391,35 @@ public class RemoteComposeBuffer { public void drawComponentContent() { DrawContent.apply(mBuffer); } + + /** + * Ensures the bitmap is stored. + * + * @param image the bitbap to store + * @return the id of the bitmap + */ + private int storeBitmap(Object image) { + int imageId = mRemoteComposeState.dataGetId(image); + if (imageId == -1) { + imageId = mRemoteComposeState.cacheData(image); + byte[] data = mPlatform.imageToByteArray(image); // todo: potential npe + short imageWidth = (short) mPlatform.getImageWidth(image); + short imageHeight = (short) mPlatform.getImageHeight(image); + if (mPlatform.isAlpha8Image(image)) { + BitmapData.apply( + mBuffer, + imageId, + BitmapData.TYPE_PNG_ALPHA_8, + imageWidth, + BitmapData.ENCODING_INLINE, + imageHeight, + data); // todo: potential npe + } else { + BitmapData.apply( + mBuffer, imageId, imageWidth, imageHeight, data); // todo: potential npe + } + } + + return imageId; + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java index 363b82bdf70c..83c06197db9c 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java @@ -27,6 +27,7 @@ import com.android.internal.widget.remotecompose.core.operations.utilities.IntMa import com.android.internal.widget.remotecompose.core.operations.utilities.NanMap; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; /** @@ -36,7 +37,7 @@ import java.util.HashMap; public class RemoteComposeState implements CollectionsAccess { public static final int START_ID = 42; // private static final int MAX_FLOATS = 500; - private static final int MAX_COLORS = 200; + private static int sMaxColors = 200; private static final int MAX_DATA = 1000; private final IntMap<Object> mIntDataMap = new IntMap<>(); @@ -52,7 +53,7 @@ public class RemoteComposeState implements CollectionsAccess { private final IntMap<Object> mPathMap = new IntMap<>(); private final IntMap<float[]> mPathData = new IntMap<>(); - private final boolean[] mColorOverride = new boolean[MAX_COLORS]; + private boolean[] mColorOverride = new boolean[sMaxColors]; @NonNull private final IntMap<ArrayAccess> mCollectionMap = new IntMap<>(); private final boolean[] mDataOverride = new boolean[MAX_DATA]; @@ -318,7 +319,7 @@ public class RemoteComposeState implements CollectionsAccess { * @param color */ public void updateColor(int id, int color) { - if (mColorOverride[id]) { + if (id < sMaxColors && mColorOverride[id]) { return; } mColorMap.put(id, color); @@ -342,6 +343,10 @@ public class RemoteComposeState implements CollectionsAccess { * @param color */ public void overrideColor(int id, int color) { + if (id >= sMaxColors) { + sMaxColors *= 2; + mColorOverride = Arrays.copyOf(mColorOverride, sMaxColors); + } mColorOverride[id] = true; mColorMap.put(id, color); updateListeners(id); diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java index 622f0c8d12b7..c6b17e4116d6 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java @@ -211,6 +211,14 @@ public abstract class RemoteContext { public abstract void clearNamedFloatOverride(String floatName); /** + * Set the value of a named long. This modifies the content of a LongConstant + * + * @param name the name of the float to override + * @param value Override the default float + */ + public abstract void setNamedLong(String name, long value); + + /** * Set the value of a named Object. This overrides the Object in the document * * @param dataName the name of the Object to override diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java index 13e6f3859933..255d7a46e49e 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java @@ -72,6 +72,9 @@ public class BitmapData extends Operation implements SerializableToString, Seria /** The data is encoded as RAW 8888 bit */ public static final short TYPE_RAW8888 = 3; + /** The data is encoded as PNG_8888 but decoded as ALPHA_8 */ + public static final short TYPE_PNG_ALPHA_8 = 4; + /** * create a bitmap structure * @@ -136,6 +139,15 @@ public class BitmapData extends Operation implements SerializableToString, Seria } /** + * The type of the image + * + * @return the type of the image + */ + public int getType() { + return mType; + } + + /** * Add the image to the document * * @param buffer document to write to @@ -195,6 +207,21 @@ public class BitmapData extends Operation implements SerializableToString, Seria int imageId = buffer.readInt(); int width = buffer.readInt(); int height = buffer.readInt(); + int type; + if (width > 0xffff) { + type = width >> 16; + width = width & 0xffff; + } else { + type = TYPE_PNG_8888; + } + + int encoding; + if (height > 0xffff) { + encoding = height >> 16; + height = height & 0xffff; + } else { + encoding = ENCODING_INLINE; + } if (width < 1 || height < 1 || height > MAX_IMAGE_DIMENSION @@ -202,7 +229,10 @@ public class BitmapData extends Operation implements SerializableToString, Seria throw new RuntimeException("Dimension of image is invalid " + width + "x" + height); } byte[] bitmap = buffer.readBuffer(); - operations.add(new BitmapData(imageId, width, height, bitmap)); + BitmapData bitmapData = new BitmapData(imageId, width, height, bitmap); + bitmapData.mType = (short) type; + bitmapData.mEncoding = (short) encoding; + operations.add(bitmapData); } /** @@ -244,7 +274,7 @@ public class BitmapData extends Operation implements SerializableToString, Seria @Override public void serialize(MapSerializer serializer) { serializer - .add("type", CLASS_NAME) + .addType(CLASS_NAME) .add("imageId", mImageId) .add("imageWidth", mImageWidth) .add("imageHeight", mImageHeight) @@ -275,6 +305,8 @@ public class BitmapData extends Operation implements SerializableToString, Seria return "TYPE_RAW8"; case TYPE_RAW8888: return "TYPE_RAW8888"; + case TYPE_PNG_ALPHA_8: + return "TYPE_PNG_ALPHA_8"; default: return "TYPE_INVALID"; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapFontData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapFontData.java index 078ce981d243..70bda6d1c0fa 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapFontData.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapFontData.java @@ -221,6 +221,6 @@ public class BitmapFontData extends Operation implements Serializable { @Override public void serialize(MapSerializer serializer) { - serializer.add("type", CLASS_NAME).add("id", mId); + serializer.addType(CLASS_NAME).add("id", mId); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java index 00ac9c28f362..7a8373bf7c7d 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java @@ -237,7 +237,7 @@ public class ClickArea extends Operation @Override public void serialize(MapSerializer serializer) { serializer - .add("type", CLASS_NAME) + .addType(CLASS_NAME) .add("id", mId) .add("contentDescriptionId", mContentDescription) .add("left", mLeft, mOutLeft) diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java index e7dc405b542e..03bdb70a9289 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java @@ -133,7 +133,7 @@ public class ClipPath extends PaintOperation implements Serializable { @Override public void serialize(MapSerializer serializer) { - serializer.add("type", CLASS_NAME).add("id", mId).add("regionOp", regionOpToString()); + serializer.addType(CLASS_NAME).add("id", mId).add("regionOp", regionOpToString()); } String regionOpToString() { diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java index 1646146bd21d..8b1e96a691a6 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java @@ -118,6 +118,6 @@ public class ClipRect extends DrawBase4 { @Override public void serialize(MapSerializer serializer) { - serialize(serializer, "left", "top", "right", "bottom").add("type", CLASS_NAME); + serialize(serializer, "left", "top", "right", "bottom").addType(CLASS_NAME); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java index 333ffaae8289..7b12f4a9cf15 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java @@ -133,7 +133,7 @@ public class ColorConstant extends Operation implements Serializable { @Override public void serialize(MapSerializer serializer) { serializer - .add("type", CLASS_NAME) + .addType(CLASS_NAME) .add("color", Utils.colorInt(mColor)) .add("colorId", mColorId); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java index d5af7914607a..25323a8a818d 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java @@ -507,7 +507,7 @@ public class ColorExpression extends Operation implements VariableSupport, Seria @Override public void serialize(MapSerializer serializer) { - serializer.add("type", CLASS_NAME).add("id", mId); + serializer.addType(CLASS_NAME).add("id", mId); switch (mMode) { case COLOR_COLOR_INTERPOLATE: case ID_COLOR_INTERPOLATE: diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java index cd13d2534266..5335e4fa76ca 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java @@ -171,7 +171,7 @@ public class ComponentValue extends Operation implements SerializableToString, S @Override public void serialize(MapSerializer serializer) { serializer - .add("type", CLASS_NAME) + .addType(CLASS_NAME) .add("valueId", mValueId) .add("componentValueType", typeToString(mType)) .add("componentId", mComponentID); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java index fb3abdbb0626..20bebaa1ddb2 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java @@ -148,8 +148,9 @@ public class DataListFloat extends Operation implements VariableSupport, ArrayAc return mValues.length; } + @SuppressWarnings("JdkImmutableCollections") @Override public void serialize(MapSerializer serializer) { - serializer.add("type", CLASS_NAME).add("id", mId).add("values", List.of(mValues)); + serializer.addType(CLASS_NAME).add("id", mId).add("values", List.of(mValues)); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java index 58fd74fdcf3e..af660f3d42c9 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java @@ -150,8 +150,9 @@ public class DataListIds extends Operation implements VariableSupport, ArrayAcce return 0; } + @SuppressWarnings("JdkImmutableCollections") @Override public void serialize(MapSerializer serializer) { - serializer.add("type", CLASS_NAME).add("id", mId).add("ids", List.of(mIds)); + serializer.addType(CLASS_NAME).add("id", mId).add("ids", List.of(mIds)); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java index a427836b6c5f..5024164651f5 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java @@ -141,6 +141,6 @@ public class DrawArc extends DrawBase6 { @Override public void serialize(MapSerializer serializer) { serialize(serializer, "left", "top", "right", "bottom", "startAngle", "sweepAngle") - .add("type", CLASS_NAME); + .addType(CLASS_NAME); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java index 40d3bede0912..e3b53a125a63 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java @@ -28,6 +28,7 @@ import com.android.internal.widget.remotecompose.core.RemoteContext; import com.android.internal.widget.remotecompose.core.VariableSupport; import com.android.internal.widget.remotecompose.core.WireBuffer; import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; +import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; import java.util.List; @@ -185,4 +186,16 @@ public class DrawBitmap extends PaintOperation implements VariableSupport { public void paint(@NonNull PaintContext context) { context.drawBitmap(mId, mOutputLeft, mOutputTop, mOutputRight, mOutputBottom); } + + @Override + public void serialize(MapSerializer serializer) { + serializer + .addType(CLASS_NAME) + .add("imageId", mId) + .add("contentDescriptionId", mDescriptionId) + .add("left", mLeft, mOutputLeft) + .add("top", mTop, mOutputTop) + .add("right", mRight, mOutputRight) + .add("bottom", mBottom, mOutputBottom); + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapFontText.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapFontText.java index 258988e8b00a..bff87fda2fd9 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapFontText.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapFontText.java @@ -28,6 +28,7 @@ import com.android.internal.widget.remotecompose.core.VariableSupport; import com.android.internal.widget.remotecompose.core.WireBuffer; import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation; +import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; import java.util.List; @@ -229,4 +230,16 @@ public class DrawBitmapFontText extends PaintOperation implements VariableSuppor xPos = xPos2 + glyph.mMarginRight; } } + + @Override + public void serialize(MapSerializer serializer) { + serializer + .addType(CLASS_NAME) + .add("textId", mTextID) + .add("bitmapFontId", mBitmapFontID) + .add("start", mStart) + .add("end", mEnd) + .add("x", mX, mOutX) + .add("y", mY, mOutY); + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java index 013dd1ae9db8..39d85af900ee 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java @@ -25,6 +25,7 @@ import com.android.internal.widget.remotecompose.core.WireBuffer; import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation; import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent; +import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; import java.util.List; @@ -230,4 +231,20 @@ public class DrawBitmapInt extends PaintOperation implements AccessibleComponent mDstBottom, mContentDescId); } + + @Override + public void serialize(MapSerializer serializer) { + serializer + .addType(CLASS_NAME) + .add("imageId", mImageId) + .add("contentDescriptionId", mContentDescId) + .add("srcLeft", mSrcLeft) + .add("srcTop", mSrcTop) + .add("srcRight", mSrcRight) + .add("srcBottom", mSrcBottom) + .add("dstLeft", mDstLeft) + .add("dstTop", mDstTop) + .add("dstRight", mDstRight) + .add("dstBottom", mDstBottom); + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java index e1070f97d5aa..827e5692e132 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java @@ -28,6 +28,7 @@ import com.android.internal.widget.remotecompose.core.documentation.Documentatio import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation; import com.android.internal.widget.remotecompose.core.operations.utilities.ImageScaling; import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent; +import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; import java.util.List; @@ -374,4 +375,46 @@ public class DrawBitmapScaled extends PaintOperation mContentDescId); context.restore(); } + + @Override + public void serialize(MapSerializer serializer) { + serializer + .addType(CLASS_NAME) + .add("imageId", mImageId) + .add("contentDescriptionId", mContentDescId) + .add("scaleType", getScaleTypeString()) + .add("mode", mMode) + .add("scaleFactor", mScaleFactor, mOutScaleFactor) + .add("srcLeft", mSrcLeft, mOutSrcLeft) + .add("srcTop", mSrcTop, mOutSrcTop) + .add("srcRight", mSrcRight, mOutSrcRight) + .add("srcBottom", mSrcBottom, mOutSrcBottom) + .add("dstLeft", mDstLeft, mOutDstLeft) + .add("dstTop", mDstTop, mOutDstTop) + .add("dstRight", mDstRight, mOutDstRight) + .add("dstBottom", mDstBottom, mOutDstBottom); + } + + private String getScaleTypeString() { + switch (mScaleType) { + case SCALE_NONE: + return "SCALE_NONE"; + case SCALE_INSIDE: + return "SCALE_INSIDE"; + case SCALE_FILL_WIDTH: + return "SCALE_FILL_WIDTH"; + case SCALE_FILL_HEIGHT: + return "SCALE_FILL_HEIGHT"; + case SCALE_FIT: + return "SCALE_FIT"; + case SCALE_CROP: + return "SCALE_CROP"; + case SCALE_FILL_BOUNDS: + return "SCALE_FILL_BOUNDS"; + case SCALE_FIXED_SCALE: + return "SCALE_FIXED_SCALE"; + default: + return "INVALID_SCALE_TYPE"; + } + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java index dfc89b12de1e..538cbaf97a06 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java @@ -112,6 +112,6 @@ public class DrawCircle extends DrawBase3 { @Override public void serialize(MapSerializer serializer) { - serialize(serializer, "cx", "cy", "radius").add("type", CLASS_NAME); + serialize(serializer, "cx", "cy", "radius").addType(CLASS_NAME); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawContent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawContent.java index e2e22acbeb8f..4d2a939eb948 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawContent.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawContent.java @@ -114,6 +114,6 @@ public class DrawContent extends PaintOperation implements Serializable { @Override public void serialize(MapSerializer serializer) { - serializer.add("type", CLASS_NAME); + serializer.addType(CLASS_NAME); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java index 461dece02625..97e5057fb950 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java @@ -140,6 +140,6 @@ public class DrawLine extends DrawBase4 implements SerializableToString { @Override public void serialize(MapSerializer serializer) { - serialize(serializer, "startX", "startY", "endX", "endY").add("type", CLASS_NAME); + serialize(serializer, "startX", "startY", "endX", "endY").addType(CLASS_NAME); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java index d0a5adc0e737..5d619baa1328 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java @@ -110,6 +110,6 @@ public class DrawOval extends DrawBase4 { @Override public void serialize(MapSerializer serializer) { - serialize(serializer, "left", "top", "right", "bottom").add("type", CLASS_NAME); + serialize(serializer, "left", "top", "right", "bottom").addType(CLASS_NAME); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java index 3fd8bb440d82..6e299275fb24 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java @@ -112,6 +112,6 @@ public class DrawPath extends PaintOperation implements Serializable { @Override public void serialize(MapSerializer serializer) { - serializer.add("type", CLASS_NAME).add("id", mId).add("start", mStart).add("end", mEnd); + serializer.addType(CLASS_NAME).add("id", mId).add("start", mStart).add("end", mEnd); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java index f6aa30f5b5dd..15f3ced74652 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java @@ -106,6 +106,6 @@ public class DrawRect extends DrawBase4 { @Override public void serialize(MapSerializer serializer) { - serialize(serializer, "left", "top", "right", "bottom").add("type", CLASS_NAME); + serialize(serializer, "left", "top", "right", "bottom").addType(CLASS_NAME); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java index 67338c14a101..31d9b6ac124d 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java @@ -127,6 +127,6 @@ public class DrawRoundRect extends DrawBase6 { @Override public void serialize(MapSerializer serializer) { serialize(serializer, "left", "top", "right", "bottom", "rx", "sweepAngle") - .add("type", CLASS_NAME); + .addType(CLASS_NAME); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java index 78f64a5bcd44..19f16239b9f2 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java @@ -128,6 +128,6 @@ public class DrawSector extends DrawBase6 { @Override public void serialize(MapSerializer serializer) { serialize(serializer, "left", "top", "right", "bottom", "startAngle", "sweepAngle") - .add("type", CLASS_NAME); + .addType(CLASS_NAME); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java index 8adba1d2616a..ee1689c21fe3 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java @@ -28,6 +28,7 @@ import com.android.internal.widget.remotecompose.core.VariableSupport; import com.android.internal.widget.remotecompose.core.WireBuffer; import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation; +import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; import java.util.List; @@ -205,4 +206,17 @@ public class DrawText extends PaintOperation implements VariableSupport { public void paint(@NonNull PaintContext context) { context.drawTextRun(mTextID, mStart, mEnd, mContextStart, mContextEnd, mOutX, mOutY, mRtl); } + + @Override + public void serialize(MapSerializer serializer) { + serializer + .addType(CLASS_NAME) + .add("start", mStart) + .add("end", mEnd) + .add("contextStart", mContextStart) + .add("contextEnd", mContextEnd) + .add("x", mX, mOutX) + .add("y", mY, mOutY) + .add("rtl", mRtl); + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java index 92469d1834ba..1d917d565331 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java @@ -244,7 +244,7 @@ public class DrawTextAnchored extends PaintOperation implements VariableSupport, @Override public void serialize(MapSerializer serializer) { serializer - .add("type", CLASS_NAME) + .addType(CLASS_NAME) .add("textId", mTextID) .add("x", mX, mOutX) .add("y", mY, mOutY) diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java index 1f7910ede4b5..f382440360ed 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java @@ -159,7 +159,7 @@ public class DrawTextOnPath extends PaintOperation implements VariableSupport, S @Override public void serialize(MapSerializer serializer) { serializer - .add("type", CLASS_NAME) + .addType(CLASS_NAME) .add("pathId", mPathId) .add("textId", mTextId) .add("vOffset", mVOffset, mOutVOffset) diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java index e2883949022c..4ca995b59f1f 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java @@ -28,6 +28,7 @@ import com.android.internal.widget.remotecompose.core.VariableSupport; import com.android.internal.widget.remotecompose.core.WireBuffer; import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation; +import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; import java.util.List; @@ -171,4 +172,15 @@ public class DrawTweenPath extends PaintOperation implements VariableSupport { public void paint(@NonNull PaintContext context) { context.drawTweenPath(mPath1Id, mPath2Id, mOutTween, mOutStart, mOutStop); } + + @Override + public void serialize(MapSerializer serializer) { + serializer + .addType(CLASS_NAME) + .add("path1Id", mPath1Id) + .add("path2Id", mPath2Id) + .add("tween", mTween, mOutTween) + .add("start", mStart, mOutStart) + .add("stop", mStop, mOutStop); + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java index 7f3c3ed6bcff..233e246d3868 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java @@ -123,6 +123,6 @@ public class FloatConstant extends Operation implements Serializable { @Override public void serialize(MapSerializer serializer) { - serializer.add("type", CLASS_NAME).add("id", mId).add("value", mValue); + serializer.addType(CLASS_NAME).add("id", mId).add("value", mValue); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java index c1fa898ec619..eba201bfb216 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java @@ -160,6 +160,9 @@ public class FloatExpression extends Operation implements VariableSupport, Seria mLastAnimatedValue = lastComputedValue; context.loadFloat(mId, lastComputedValue); context.needsRepaint(); + if (mFloatAnimation.isPropagate()) { + markDirty(); + } } } else if (mSpring != null) { float lastComputedValue = mSpring.get(t - mLastChange); @@ -169,8 +172,12 @@ public class FloatExpression extends Operation implements VariableSupport, Seria context.needsRepaint(); } } else { - float v = - mExp.eval(context.getCollectionsAccess(), mPreCalcValue, mPreCalcValue.length); + float v = 0; + try { + v = mExp.eval(context.getCollectionsAccess(), mPreCalcValue, mPreCalcValue.length); + } catch (Exception e) { + throw new RuntimeException(this.toString() + " len = " + mPreCalcValue.length, e); + } if (mFloatAnimation != null) { mFloatAnimation.setTargetValue(v); } @@ -344,7 +351,7 @@ public class FloatExpression extends Operation implements VariableSupport, Seria public void serialize(MapSerializer serializer) { serializer .addTags(SerializeTags.EXPRESSION) - .add("type", CLASS_NAME) + .addType(CLASS_NAME) .add("id", mId) .addFloatExpressionSrc("srcValues", mSrcValue) .add("animation", mFloatAnimation); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionCall.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionCall.java index eccc00a18308..85592948438f 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionCall.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatFunctionCall.java @@ -32,8 +32,10 @@ import com.android.internal.widget.remotecompose.core.documentation.Documentatio import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation; import com.android.internal.widget.remotecompose.core.operations.utilities.AnimatedFloatExpression; import com.android.internal.widget.remotecompose.core.operations.utilities.NanMap; +import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** This provides the command to call a floatfunction defined in floatfunction */ @@ -182,4 +184,13 @@ public class FloatFunctionCall extends PaintOperation implements VariableSupport } mFunction.execute(remoteContext); } + + @Override + public void serialize(MapSerializer serializer) { + serializer + .addType(CLASS_NAME) + .add("id", mId) + .add("args", Collections.singletonList(mArgs)) + .add("outArgs", Collections.singletonList(mOutArgs)); + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ImageAttribute.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ImageAttribute.java index fb4a5e4848c2..9ef1b3b1ad6a 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/ImageAttribute.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ImageAttribute.java @@ -26,7 +26,9 @@ import com.android.internal.widget.remotecompose.core.PaintContext; import com.android.internal.widget.remotecompose.core.PaintOperation; import com.android.internal.widget.remotecompose.core.WireBuffer; import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; +import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; +import java.util.Collections; import java.util.List; /** Operation to extract meta Attributes from image data objects */ @@ -162,4 +164,25 @@ public class ImageAttribute extends PaintOperation { break; } } + + @Override + public void serialize(MapSerializer serializer) { + serializer + .addType(CLASS_NAME) + .add("id", mId) + .add("imageId", mImageId) + .add("args", Collections.singletonList(mArgs)) + .addType(typeToString()); + } + + private String typeToString() { + switch (mType) { + case IMAGE_WIDTH: + return "IMAGE_WIDTH"; + case IMAGE_HEIGHT: + return "IMAGE_HEIGHT"; + default: + return "INVALID_TYPE"; + } + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java index 2a5260c0c9b1..53b3c89f3aa8 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java @@ -233,7 +233,7 @@ public class IntegerExpression extends Operation implements VariableSupport, Ser public void serialize(MapSerializer serializer) { serializer .addTags(SerializeTags.EXPRESSION) - .add("type", CLASS_NAME) + .addType(CLASS_NAME) .add("id", mId) .add("mask", mId) .addIntExpressionSrc("srcValues", mSrcValue, mMask); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java index 64df19d8a04f..d2b38f42e966 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java @@ -102,6 +102,6 @@ public class MatrixRestore extends PaintOperation implements Serializable { @Override public void serialize(MapSerializer serializer) { - serializer.add("type", CLASS_NAME); + serializer.addType(CLASS_NAME); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java index 9c4df0b54d60..5990b4bdfad7 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java @@ -114,6 +114,6 @@ public class MatrixRotate extends DrawBase3 { @Override public void serialize(MapSerializer serializer) { - serialize(serializer, "rotate", "pivotX", "pivotY").add("type", CLASS_NAME); + serialize(serializer, "rotate", "pivotX", "pivotY").addType(CLASS_NAME); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java index 0e6de0db3b28..06da1565bf3a 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java @@ -100,6 +100,6 @@ public class MatrixSave extends PaintOperation implements Serializable { @Override public void serialize(MapSerializer serializer) { - serializer.add("type", CLASS_NAME); + serializer.addType(CLASS_NAME); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java index b6e5cbcf388a..20d406569223 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java @@ -106,6 +106,6 @@ public class MatrixScale extends DrawBase4 { @Override public void serialize(MapSerializer serializer) { - serialize(serializer, "scaleX", "scaleY", "pivotX", "pivotY").add("type", CLASS_NAME); + serialize(serializer, "scaleX", "scaleY", "pivotX", "pivotY").addType(CLASS_NAME); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java index f9a589c598aa..6d1c503c3284 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java @@ -103,6 +103,6 @@ public class MatrixSkew extends DrawBase2 { @Override public void serialize(MapSerializer serializer) { - serialize(serializer, "skewX", "skewY").add("type", CLASS_NAME); + serialize(serializer, "skewX", "skewY").addType(CLASS_NAME); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java index de783bfc7ae8..e21f13355679 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java @@ -102,6 +102,6 @@ public class MatrixTranslate extends DrawBase2 { @Override public void serialize(MapSerializer serializer) { - serialize(serializer, "dx", "dy").add("type", CLASS_NAME); + serialize(serializer, "dx", "dy").addType(CLASS_NAME); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java b/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java index 96628fd51225..9a880858b5a5 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java @@ -25,7 +25,6 @@ import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.RemoteContext; import com.android.internal.widget.remotecompose.core.WireBuffer; import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; -import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation; import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; import com.android.internal.widget.remotecompose.core.serialize.Serializable; @@ -43,6 +42,8 @@ public class NamedVariable extends Operation implements Serializable { public static final int FLOAT_TYPE = 1; public static final int STRING_TYPE = 0; public static final int IMAGE_TYPE = 3; + public static final int INT_TYPE = 4; + public static final int LONG_TYPE = 5; public NamedVariable(int varId, int varType, @NonNull String name) { this.mVarId = varId; @@ -122,7 +123,7 @@ public class NamedVariable extends Operation implements Serializable { public static void documentation(@NonNull DocumentationBuilder doc) { doc.operation("Data Operations", OP_CODE, CLASS_NAME) .description("Add a string name for an ID") - .field(DocumentedOperation.INT, "varId", "id to label") + .field(INT, "varId", "id to label") .field(INT, "varType", "The type of variable") .field(UTF8, "name", "String"); } @@ -141,7 +142,7 @@ public class NamedVariable extends Operation implements Serializable { @Override public void serialize(MapSerializer serializer) { serializer - .add("type", CLASS_NAME) + .addType(CLASS_NAME) .add("varId", mVarId) .add("varName", mVarName) .add("varType", typeToString()); @@ -157,6 +158,8 @@ public class NamedVariable extends Operation implements Serializable { return "STRING_TYPE"; case IMAGE_TYPE: return "IMAGE_TYPE"; + case INT_TYPE: + return "INT_TYPE"; default: return "INVALID_TYPE"; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java index 8389aa707ee6..70197c6d085d 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java @@ -131,6 +131,6 @@ public class PaintData extends PaintOperation implements VariableSupport, Serial @Override public void serialize(MapSerializer serializer) { - serializer.add("type", CLASS_NAME).add("paintBundle", mPaintData); + serializer.addType(CLASS_NAME).add("paintBundle", mPaintData); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesLoop.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesLoop.java index 8d19c94df604..f9fdfdf09e35 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesLoop.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesLoop.java @@ -33,6 +33,7 @@ import com.android.internal.widget.remotecompose.core.documentation.DocumentedOp import com.android.internal.widget.remotecompose.core.operations.layout.Container; import com.android.internal.widget.remotecompose.core.operations.utilities.AnimatedFloatExpression; import com.android.internal.widget.remotecompose.core.operations.utilities.NanMap; +import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; import java.util.ArrayList; import java.util.List; @@ -292,4 +293,9 @@ public class ParticlesLoop extends PaintOperation implements VariableSupport, Co } } } + + @Override + public void serialize(MapSerializer serializer) { + serializer.addType(CLASS_NAME).add("id", mId); + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java index 8f353ce4a26b..8a747e134897 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java @@ -258,9 +258,6 @@ public class PathAppend extends PaintOperation implements VariableSupport, Seria @Override public void serialize(MapSerializer serializer) { - serializer - .add("type", CLASS_NAME) - .add("id", mInstanceId) - .add("path", pathString(mFloatPath)); + serializer.addType(CLASS_NAME).add("id", mInstanceId).add("path", pathString(mFloatPath)); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java index 7aa3390b53ee..78e3b9eac110 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java @@ -242,9 +242,6 @@ public class PathCreate extends PaintOperation implements VariableSupport, Seria @Override public void serialize(MapSerializer serializer) { - serializer - .add("type", CLASS_NAME) - .add("id", mInstanceId) - .add("path", pathString(mFloatPath)); + serializer.addType(CLASS_NAME).add("id", mInstanceId).add("path", pathString(mFloatPath)); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java index 9564f15209c7..cedc4f3b0e45 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java @@ -243,9 +243,6 @@ public class PathData extends Operation implements VariableSupport, Serializable @Override public void serialize(MapSerializer serializer) { - serializer - .add("type", CLASS_NAME) - .add("id", mInstanceId) - .add("path", pathString(mFloatPath)); + serializer.addType(CLASS_NAME).add("id", mInstanceId).add("path", pathString(mFloatPath)); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java index c5add57d4dd0..09b29e8c5e5c 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java @@ -161,7 +161,7 @@ public class PathTween extends PaintOperation implements VariableSupport, Serial @Override public void serialize(MapSerializer serializer) { serializer - .add("type", CLASS_NAME) + .addType(CLASS_NAME) .add("outId", mOutId) .add("pathId1", mPathId1) .add("pathId2", mPathId2) diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java index a6a8a810d2ad..214d240a39fd 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java @@ -133,6 +133,6 @@ public class RootContentDescription extends Operation @Override public void serialize(MapSerializer serializer) { - serializer.add("type", CLASS_NAME).add("contentDescriptionId", mContentDescription); + serializer.addType(CLASS_NAME).add("contentDescriptionId", mContentDescription); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java index 5f6162b68e9e..013b6f64b91a 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java @@ -390,7 +390,7 @@ public class ShaderData extends Operation implements VariableSupport, Serializab @Override public void serialize(MapSerializer serializer) { serializer - .add("type", CLASS_NAME) + .addType(CLASS_NAME) .add("shaderTextId", mShaderTextId) .add("shaderID", mShaderID) .add("uniformRawFloatMap", mUniformRawFloatMap) diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextAttribute.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextAttribute.java index 3e72995de9db..36f25c6ce944 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextAttribute.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextAttribute.java @@ -173,7 +173,7 @@ public class TextAttribute extends PaintOperation implements Serializable { @Override public void serialize(MapSerializer serializer) { serializer - .add("type", CLASS_NAME) + .addType(CLASS_NAME) .add("id", mId) .add("textId", mTextId) .add("measureType", typeToString()); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java index 419e6d074479..67773d1d6187 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java @@ -136,6 +136,6 @@ public class TextData extends Operation implements SerializableToString, Seriali @Override public void serialize(MapSerializer serializer) { - serializer.add("type", CLASS_NAME).add("textId", mTextId).add("text", mText); + serializer.addType(CLASS_NAME).add("textId", mTextId).add("text", mText); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java index 6b2f49be76f0..f22369f51ce9 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java @@ -215,7 +215,7 @@ public class TextFromFloat extends Operation implements VariableSupport, Seriali @Override public void serialize(MapSerializer serializer) { serializer - .add("type", CLASS_NAME) + .addType(CLASS_NAME) .add("textId", mTextId) .add("value", mValue, mOutValue) .add("digitsBefore", mDigitsBefore) diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java index e8865c26db12..fa44bf165cd7 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java @@ -156,7 +156,7 @@ public class TextLookup extends Operation implements VariableSupport, Serializab @Override public void serialize(MapSerializer serializer) { serializer - .add("type", CLASS_NAME) + .addType(CLASS_NAME) .add("textId", mTextId) .add("dataSetId", mDataSetId) .add("indexId", mIndex, mOutIndex); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java index de2025569d46..5ec3290c445d 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java @@ -149,7 +149,7 @@ public class TextLookupInt extends Operation implements VariableSupport, Seriali @Override public void serialize(MapSerializer serializer) { serializer - .add("type", CLASS_NAME) + .addType(CLASS_NAME) .add("textId", mTextId) .add("dataSetId", mDataSetId) .add("indexId", mIndex, mOutIndex); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java index 58cd68e2a5db..3559d1dbe53b 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java @@ -27,6 +27,7 @@ import com.android.internal.widget.remotecompose.core.PaintContext; import com.android.internal.widget.remotecompose.core.PaintOperation; import com.android.internal.widget.remotecompose.core.WireBuffer; import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; +import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; import java.util.List; @@ -161,4 +162,33 @@ public class TextMeasure extends PaintOperation { break; } } + + @Override + public void serialize(MapSerializer serializer) { + serializer + .addType(CLASS_NAME) + .add("id", mId) + .add("textId", mTextId) + .add("measureType", typeToString()); + } + + private String typeToString() { + int val = mType & 255; + switch (val) { + case MEASURE_WIDTH: + return "MEASURE_WIDTH"; + case MEASURE_HEIGHT: + return "MEASURE_HEIGHT"; + case MEASURE_LEFT: + return "MEASURE_LEFT"; + case MEASURE_TOP: + return "MEASURE_TOP"; + case MEASURE_RIGHT: + return "MEASURE_RIGHT"; + case MEASURE_BOTTOM: + return "MEASURE_BOTTOM"; + default: + return "INVALID_TYPE"; + } + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java index 262916dd9d0c..1239b5648446 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java @@ -132,7 +132,7 @@ public class TextMerge extends Operation implements Serializable { @Override public void serialize(MapSerializer serializer) { serializer - .add("type", CLASS_NAME) + .addType(CLASS_NAME) .add("id", mTextId) .add("leftId", mSrcId1) .add("rightId", mSrcId2); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java index afb84b56d7b0..fd9a2bf5e435 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TimeAttribute.java @@ -27,12 +27,14 @@ import com.android.internal.widget.remotecompose.core.PaintOperation; import com.android.internal.widget.remotecompose.core.RemoteContext; import com.android.internal.widget.remotecompose.core.WireBuffer; import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; +import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; import com.android.internal.widget.remotecompose.core.types.LongConstant; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.Arrays; +import java.util.Collections; import java.util.List; /** Operation to perform time related calculation */ @@ -292,4 +294,48 @@ public class TimeAttribute extends PaintOperation { break; } } + + @Override + public void serialize(MapSerializer serializer) { + serializer + .addType(CLASS_NAME) + .add("id", mId) + .add("timeId", mTimeId) + .addType(getTypeString()) + .add("args", Collections.singletonList(mArgs)); + } + + private String getTypeString() { + int val = mType & 255; + switch (val) { + case TIME_FROM_NOW_SEC: + return "TIME_FROM_NOW_SEC"; + case TIME_FROM_NOW_MIN: + return "TIME_FROM_NOW_MIN"; + case TIME_FROM_NOW_HR: + return "TIME_FROM_NOW_HR"; + case TIME_FROM_ARG_SEC: + return "TIME_FROM_ARG_SEC"; + case TIME_FROM_ARG_MIN: + return "TIME_FROM_ARG_MIN"; + case TIME_FROM_ARG_HR: + return "TIME_FROM_ARG_HR"; + case TIME_IN_SEC: + return "TIME_IN_SEC"; + case TIME_IN_MIN: + return "TIME_IN_MIN"; + case TIME_IN_HR: + return "TIME_IN_HR"; + case TIME_DAY_OF_MONTH: + return "TIME_DAY_OF_MONTH"; + case TIME_MONTH_VALUE: + return "TIME_MONTH_VALUE"; + case TIME_DAY_OF_WEEK: + return "TIME_DAY_OF_WEEK"; + case TIME_YEAR: + return "TIME_YEAR"; + default: + return "INVALID_TIME_TYPE"; + } + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java index 2591a4c39778..f24672922367 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java @@ -716,9 +716,9 @@ public class TouchExpression extends Operation @Override public void serialize(MapSerializer serializer) { serializer - .add("type", CLASS_NAME) + .addType(CLASS_NAME) .add("id", mId) - .add("mDefValue", mDefValue, mOutDefValue) + .add("defValue", mDefValue, mOutDefValue) .add("min", mMin, mOutMin) .add("max", mMax, mOutMax) .add("mode", mMode) diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java index 9dc2a49d26ef..b98a017addb3 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java @@ -129,6 +129,6 @@ public class AnimatableValue implements Serializable { @Override public void serialize(MapSerializer serializer) { - serializer.add("type", "AnimatableValue").add("id", mId); + serializer.addType("AnimatableValue").add("id", mId); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasOperations.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasOperations.java index 3e7f1d304315..25a10ab7dbeb 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasOperations.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasOperations.java @@ -156,7 +156,7 @@ public class CanvasOperations extends PaintOperation @Override public void serialize(MapSerializer serializer) { - serializer.add("type", CLASS_NAME).add("list", mList); + serializer.addType(CLASS_NAME).add("list", mList); } /** diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java index 8b13c13a3333..7ee14903ced5 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java @@ -253,6 +253,6 @@ public class ClickModifierOperation extends PaintOperation @Override public void serialize(MapSerializer serializer) { - serializer.addTags(SerializeTags.MODIFIER).add("type", "ClickModifierOperation"); + serializer.addTags(SerializeTags.MODIFIER).addType("ClickModifierOperation"); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java index c73643682b55..b30dade828a7 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java @@ -57,8 +57,8 @@ public class Component extends PaintOperation protected float mHeight; @Nullable protected Component mParent; protected int mAnimationId = -1; - @NonNull public Visibility mVisibility = Visibility.VISIBLE; - @NonNull public Visibility mScheduledVisibility = Visibility.VISIBLE; + public int mVisibility = Visibility.VISIBLE; + public int mScheduledVisibility = Visibility.VISIBLE; @NonNull public ArrayList<Operation> mList = new ArrayList<>(); public PaintOperation mPreTranslate; // todo, can we initialize this here and make it NonNull? public boolean mNeedsMeasure = true; @@ -288,22 +288,42 @@ public class Component extends PaintOperation } /** - * Returns the intrinsic width of the layout + * Returns the min intrinsic width of the layout * * @param context * @return the width in pixels */ - public float intrinsicWidth(@Nullable RemoteContext context) { + public float minIntrinsicWidth(@Nullable RemoteContext context) { return getWidth(); } /** - * Returns the intrinsic height of the layout + * Returns the max intrinsic width of the layout + * + * @param context + * @return the width in pixels + */ + public float maxIntrinsicWidth(@Nullable RemoteContext context) { + return getWidth(); + } + + /** + * Returns the min intrinsic height of the layout + * + * @param context + * @return the height in pixels + */ + public float minIntrinsicHeight(@Nullable RemoteContext context) { + return getHeight(); + } + + /** + * Returns the max intrinsic height of the layout * * @param context * @return the height in pixels */ - public float intrinsicHeight(@Nullable RemoteContext context) { + public float maxIntrinsicHeight(@Nullable RemoteContext context) { return getHeight(); } @@ -338,10 +358,119 @@ public class Component extends PaintOperation // Nothing here } - public enum Visibility { - GONE, - VISIBLE, - INVISIBLE + public static class Visibility { + + public static final int GONE = 0; + public static final int VISIBLE = 1; + public static final int INVISIBLE = 2; + public static final int OVERRIDE_GONE = 16; + public static final int OVERRIDE_VISIBLE = 32; + public static final int OVERRIDE_INVISIBLE = 64; + public static final int CLEAR_OVERRIDE = 128; + + /** + * Returns a string representation of the field + * + * @param value + * @return + */ + public static String toString(int value) { + switch (value) { + case GONE: + return "GONE"; + case VISIBLE: + return "VISIBLE"; + case INVISIBLE: + return "INVISIBLE"; + } + if ((value >> 4) > 0) { + if ((value & OVERRIDE_GONE) == OVERRIDE_GONE) { + return "OVERRIDE_GONE"; + } + if ((value & OVERRIDE_VISIBLE) == OVERRIDE_VISIBLE) { + return "OVERRIDE_VISIBLE"; + } + if ((value & OVERRIDE_INVISIBLE) == OVERRIDE_INVISIBLE) { + return "OVERRIDE_INVISIBLE"; + } + } + return "" + value; + } + + /** + * Returns true if gone + * + * @param value + * @return + */ + public static boolean isGone(int value) { + if ((value >> 4) > 0) { + return (value & OVERRIDE_GONE) == OVERRIDE_GONE; + } + return value == GONE; + } + + /** + * Returns true if visible + * + * @param value + * @return + */ + public static boolean isVisible(int value) { + if ((value >> 4) > 0) { + return (value & OVERRIDE_VISIBLE) == OVERRIDE_VISIBLE; + } + return value == VISIBLE; + } + + /** + * Returns true if invisible + * + * @param value + * @return + */ + public static boolean isInvisible(int value) { + if ((value >> 4) > 0) { + return (value & OVERRIDE_INVISIBLE) == OVERRIDE_INVISIBLE; + } + return value == INVISIBLE; + } + + /** + * Returns true if the field has an override + * + * @param value + * @return + */ + public static boolean hasOverride(int value) { + return (value >> 4) > 0; + } + + /** + * Clear the override values + * + * @param value + * @return + */ + public static int clearOverride(int value) { + return value & 15; + } + + /** + * Add an override value + * + * @param value + * @param visibility + * @return + */ + public static int add(int value, int visibility) { + int v = value & 15; + v += visibility; + if ((v & CLEAR_OVERRIDE) == CLEAR_OVERRIDE) { + v = v & 15; + } + return v; + } } /** @@ -350,13 +479,28 @@ public class Component extends PaintOperation * @return */ public boolean isVisible() { - if (mVisibility != Visibility.VISIBLE || mParent == null) { - return mVisibility == Visibility.VISIBLE; - } - if (mParent != null) { // TODO: this is always true -- bbade@ - return mParent.isVisible(); + if (mParent == null || !Visibility.isVisible(mVisibility)) { + return Visibility.isVisible(mVisibility); } - return true; + return mParent.isVisible(); + } + + /** + * Returns true if the component is gone + * + * @return + */ + public boolean isGone() { + return Visibility.isGone(mVisibility); + } + + /** + * Returns true if the component is invisible + * + * @return + */ + public boolean isInvisible() { + return Visibility.isInvisible(mVisibility); } /** @@ -364,7 +508,7 @@ public class Component extends PaintOperation * * @param visibility can be VISIBLE, INVISIBLE or GONE */ - public void setVisibility(@NonNull Visibility visibility) { + public void setVisibility(int visibility) { if (visibility != mVisibility || visibility != mScheduledVisibility) { mScheduledVisibility = visibility; invalidateMeasure(); @@ -705,7 +849,7 @@ public class Component extends PaintOperation + "] " + textContent() + " Visibility (" - + mVisibility + + Visibility.toString(mVisibility) + ") "; } @@ -732,7 +876,7 @@ public class Component extends PaintOperation + ", " + mHeight + "] " - + mVisibility; + + Visibility.toString(mVisibility); // + " [" + mNeedsMeasure + ", " + mNeedsRepaint + "]" serializer.append(indent, content); } @@ -966,7 +1110,7 @@ public class Component extends PaintOperation if (applyAnimationAsNeeded(context)) { return; } - if (mVisibility == Visibility.GONE || mVisibility == Visibility.INVISIBLE) { + if (isGone() || isInvisible()) { return; } paintingComponent(context); @@ -1071,13 +1215,13 @@ public class Component extends PaintOperation @Override public void serialize(MapSerializer serializer) { serializer.addTags(SerializeTags.COMPONENT); - serializer.add("type", getSerializedName()); + serializer.addType(getSerializedName()); serializer.add("id", mComponentId); serializer.add("x", mX); serializer.add("y", mY); serializer.add("width", mWidth); serializer.add("height", mHeight); - serializer.add("visibility", mVisibility); + serializer.add("visibility", Visibility.toString(mVisibility)); serializer.add("list", mList); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseOperation.java index e277d49325be..0e629c5d2448 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseOperation.java @@ -27,6 +27,7 @@ import com.android.internal.widget.remotecompose.core.WireBuffer; import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation; import com.android.internal.widget.remotecompose.core.operations.Utils; +import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; import java.util.ArrayList; import java.util.List; @@ -228,4 +229,12 @@ public class ImpulseOperation extends PaintOperation implements VariableSupport, public void setProcess(ImpulseProcess impulseProcess) { mProcess = impulseProcess; } + + @Override + public void serialize(MapSerializer serializer) { + serializer + .addType(CLASS_NAME) + .add("duration", mDuration, mOutDuration) + .add("startAt", mStartAt, mOutStartAt); + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseProcess.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseProcess.java index 8c9dd76c9ed5..83d4d38be66e 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseProcess.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseProcess.java @@ -157,6 +157,6 @@ public class ImpulseProcess extends PaintOperation @Override public void serialize(MapSerializer serializer) { - serializer.add("type", CLASS_NAME).add("list", mList); + serializer.addType(CLASS_NAME).add("list", mList); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java index 2b63cf246555..dda328f57a3b 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java @@ -206,12 +206,12 @@ public class LoopOperation extends PaintOperation @Override public void serialize(MapSerializer serializer) { serializer - .add("type", CLASS_NAME) + .addType(CLASS_NAME) .add("indexVariableId", mIndexVariableId) .add("until", mUntil, mUntilOut) .add("from", mFrom, mFromOut) .add("step", mStep, mStepOut) - .add("mUntilOut", mUntilOut) + .add("untilOut", mUntilOut) .add("list", mList); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java index f2503b26513e..77d3dae74558 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java @@ -77,7 +77,7 @@ public class RootLayoutComponent extends Component { + " x " + mHeight + ") " - + mVisibility; + + Visibility.toString(mVisibility); } @Override @@ -97,7 +97,7 @@ public class RootLayoutComponent extends Component { + ", " + mHeight + "] " - + mVisibility); + + Visibility.toString(mVisibility)); } /** @@ -282,6 +282,6 @@ public class RootLayoutComponent extends Component { public void serialize(MapSerializer serializer) { super.serialize(serializer); serializer.addTags(SerializeTags.COMPONENT); - serializer.add("type", "RootLayoutComponent"); + serializer.addType("RootLayoutComponent"); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java index 62b1b6cf9615..283bc7a45f85 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java @@ -129,6 +129,6 @@ public class TouchCancelModifierOperation extends ListActionsOperation implement @Override public void serialize(MapSerializer serializer) { super.serialize(serializer); - serializer.add("type", "TouchCancelModifierOperation"); + serializer.addType("TouchCancelModifierOperation"); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java index 5289fda2c74b..b010c148d122 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java @@ -131,6 +131,6 @@ public class TouchDownModifierOperation extends ListActionsOperation implements @Override public void serialize(MapSerializer serializer) { super.serialize(serializer); - serializer.add("type", "TouchDownModifierOperation"); + serializer.addType("TouchDownModifierOperation"); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java index 576c5e9281a4..bc5c10b86c96 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java @@ -129,6 +129,6 @@ public class TouchUpModifierOperation extends ListActionsOperation implements To @Override public void serialize(MapSerializer serializer) { super.serialize(serializer); - serializer.add("type", "TouchUpModifierOperation"); + serializer.addType("TouchUpModifierOperation"); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java index e5cd485967e8..1a60451ddd3b 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java @@ -143,7 +143,7 @@ public class AnimateMeasure { */ public void paint(@NonNull PaintContext context) { if (mOriginal.getVisibility() != mTarget.getVisibility()) { - if (mTarget.getVisibility() == Component.Visibility.GONE) { + if (mTarget.isGone()) { switch (mExitAnimation) { case PARTICLE: // particleAnimation(context, component, original, target, vp) @@ -229,8 +229,7 @@ public class AnimateMeasure { mParticleAnimation.animate(context, mComponent, mOriginal, mTarget, mVp); break; } - } else if (mOriginal.getVisibility() == Component.Visibility.GONE - && mTarget.getVisibility() == Component.Visibility.VISIBLE) { + } else if (mOriginal.isGone() && mTarget.isVisible()) { switch (mEnterAnimation) { case ROTATE: float px = mTarget.getX() + mTarget.getW() / 2f; @@ -323,7 +322,7 @@ public class AnimateMeasure { } else { mComponent.paintingComponent(context); } - } else if (mTarget.getVisibility() == Component.Visibility.VISIBLE) { + } else if (mTarget.isVisible()) { mComponent.paintingComponent(context); } @@ -360,7 +359,7 @@ public class AnimateMeasure { public float getVisibility() { if (mOriginal.getVisibility() == mTarget.getVisibility()) { return 1f; - } else if (mTarget.getVisibility() == Component.Visibility.VISIBLE) { + } else if (mTarget.isVisible()) { return mVp; } else { return 1 - mVp; @@ -382,7 +381,7 @@ public class AnimateMeasure { float targetY = mTarget.getY(); float targetW = mTarget.getW(); float targetH = mTarget.getH(); - Component.Visibility targetVisibility = mTarget.getVisibility(); + int targetVisibility = mTarget.getVisibility(); if (targetX != measure.getX() || targetY != measure.getY() || targetW != measure.getW() @@ -393,7 +392,13 @@ public class AnimateMeasure { mTarget.setW(measure.getW()); mTarget.setH(measure.getH()); mTarget.setVisibility(measure.getVisibility()); - mStartTime = currentTime; + // We shouldn't reset the leftover animation time here + // 1/ if we are eg fading out a component, and an updateTarget comes on, we don't want + // to restart the full animation time + // 2/ if no visibility change but quick updates come in (eg live resize) it seems + // better as well to not restart the animation time and only allows the original + // time to wrap up + // mStartTime = currentTime; } } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java index 91348f5810a1..c87bbdc9809d 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java @@ -129,7 +129,7 @@ public class AnimationSpec extends Operation implements ModifierOperation { @Override public void serialize(MapSerializer serializer) { serializer - .add("type", "AnimationSpec") + .addType("AnimationSpec") .add("motionDuration", getMotionDuration()) .add("motionEasingType", Easing.getString(getMotionEasingType())) .add("visibilityDuration", getVisibilityDuration()) diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java index 35d639e65385..6ee18bbbd7e0 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java @@ -115,8 +115,10 @@ public class BoxLayout extends LayoutManager { for (Component c : mChildrenComponents) { c.measure(context, 0f, maxWidth, 0f, maxHeight, measure); ComponentMeasure m = measure.get(c); - size.setWidth(Math.max(size.getWidth(), m.getW())); - size.setHeight(Math.max(size.getHeight(), m.getH())); + if (!m.isGone()) { + size.setWidth(Math.max(size.getWidth(), m.getW())); + size.setHeight(Math.max(size.getHeight(), m.getH())); + } } // add padding size.setWidth(Math.max(size.getWidth(), computeModifierDefinedWidth(context.getContext()))); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java index 508b685b5407..f9111dffe2c4 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java @@ -154,7 +154,7 @@ public class CanvasLayout extends BoxLayout { @Override public void serialize(MapSerializer serializer) { super.serialize(serializer); - serializer.add("type", getSerializedName()); + serializer.addType(getSerializedName()); serializer.add("horizontalPositioning", mHorizontalPositioning); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java index afc41b1873ef..b0089525af5a 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.RemoteContext; import com.android.internal.widget.remotecompose.core.WireBuffer; import com.android.internal.widget.remotecompose.core.operations.layout.Component; import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure; @@ -134,6 +135,24 @@ public class CollapsibleColumnLayout extends ColumnLayout { } @Override + public float minIntrinsicHeight(@NonNull RemoteContext context) { + float height = computeModifierDefinedHeight(context); + if (!mChildrenComponents.isEmpty()) { + height += mChildrenComponents.get(0).minIntrinsicHeight(context); + } + return height; + } + + @Override + public float minIntrinsicWidth(@NonNull RemoteContext context) { + float width = computeModifierDefinedWidth(context); + if (!mChildrenComponents.isEmpty()) { + width += mChildrenComponents.get(0).minIntrinsicWidth(context); + } + return width; + } + + @Override protected boolean hasVerticalIntrinsicDimension() { return true; } @@ -147,29 +166,54 @@ public class CollapsibleColumnLayout extends ColumnLayout { boolean verticalWrap, @NonNull MeasurePass measure, @NonNull Size size) { - super.computeWrapSize( - context, maxWidth, Float.MAX_VALUE, horizontalWrap, verticalWrap, measure, size); - } + int visibleChildren = 0; + ComponentMeasure self = measure.get(this); + self.addVisibilityOverride(Visibility.OVERRIDE_VISIBLE); + float currentMaxHeight = maxHeight; + for (Component c : mChildrenComponents) { + if (c instanceof CollapsibleColumnLayout) { + c.measure(context, 0f, maxWidth, 0f, currentMaxHeight, measure); + } else { + c.measure(context, 0f, maxWidth, 0f, Float.MAX_VALUE, measure); + } + ComponentMeasure m = measure.get(c); + if (!m.isGone()) { + size.setWidth(Math.max(size.getWidth(), m.getW())); + size.setHeight(size.getHeight() + m.getH()); + visibleChildren++; + currentMaxHeight -= m.getH(); + } + } + if (!mChildrenComponents.isEmpty()) { + size.setHeight(size.getHeight() + (mSpacedBy * (visibleChildren - 1))); + } - @Override - public boolean applyVisibility( - float selfWidth, float selfHeight, @NonNull MeasurePass measure) { float childrenWidth = 0f; float childrenHeight = 0f; - boolean changedVisibility = false; + + boolean overflow = false; for (Component child : mChildrenComponents) { ComponentMeasure childMeasure = measure.get(child); - if (childMeasure.getVisibility() == Visibility.GONE) { + if (overflow || childMeasure.isGone()) { + childMeasure.addVisibilityOverride(Visibility.OVERRIDE_GONE); continue; } - if (childrenHeight + childMeasure.getH() > selfHeight) { - childMeasure.setVisibility(Visibility.GONE); - changedVisibility = true; + float childHeight = childMeasure.getH(); + boolean childDoesNotFits = childrenHeight + childHeight > maxHeight; + if (childDoesNotFits) { + childMeasure.addVisibilityOverride(Visibility.OVERRIDE_GONE); + overflow = true; } else { - childrenHeight += childMeasure.getH(); + childrenHeight += childHeight; childrenWidth = Math.max(childrenWidth, childMeasure.getW()); + visibleChildren++; } } - return changedVisibility; + if (verticalWrap) { + size.setHeight(Math.min(maxHeight, childrenHeight)); + } + if (visibleChildren == 0 || size.getHeight() <= 0f) { + self.addVisibilityOverride(Visibility.OVERRIDE_GONE); + } } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java index 0e7eb8676f46..05f332960c16 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.RemoteContext; import com.android.internal.widget.remotecompose.core.WireBuffer; import com.android.internal.widget.remotecompose.core.operations.layout.Component; import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure; @@ -139,6 +140,24 @@ public class CollapsibleRowLayout extends RowLayout { } @Override + public float minIntrinsicWidth(@NonNull RemoteContext context) { + float width = computeModifierDefinedWidth(context); + if (!mChildrenComponents.isEmpty()) { + width += mChildrenComponents.get(0).minIntrinsicWidth(context); + } + return width; + } + + @Override + public float minIntrinsicHeight(@NonNull RemoteContext context) { + float height = computeModifierDefinedHeight(context); + if (!mChildrenComponents.isEmpty()) { + height += mChildrenComponents.get(0).minIntrinsicHeight(context); + } + return height; + } + + @Override public void computeWrapSize( @NonNull PaintContext context, float maxWidth, @@ -157,19 +176,35 @@ public class CollapsibleRowLayout extends RowLayout { float childrenWidth = 0f; float childrenHeight = 0f; boolean changedVisibility = false; + int visibleChildren = 0; + ComponentMeasure self = measure.get(this); + self.clearVisibilityOverride(); + if (selfWidth <= 0 || selfHeight <= 0) { + self.addVisibilityOverride(Visibility.OVERRIDE_GONE); + return true; + } for (Component child : mChildrenComponents) { ComponentMeasure childMeasure = measure.get(child); - if (childMeasure.getVisibility() == Visibility.GONE) { + int visibility = childMeasure.getVisibility(); + childMeasure.clearVisibilityOverride(); + if (!childMeasure.isVisible()) { continue; } - if (childrenWidth + childMeasure.getW() > selfWidth) { - childMeasure.setVisibility(Visibility.GONE); - changedVisibility = true; + if (childrenWidth + childMeasure.getW() > selfWidth + && childrenHeight + childMeasure.getH() > selfHeight) { + childMeasure.addVisibilityOverride(Visibility.OVERRIDE_GONE); + if (visibility != childMeasure.getVisibility()) { + changedVisibility = true; + } } else { childrenWidth += childMeasure.getW(); childrenHeight = Math.max(childrenHeight, childMeasure.getH()); + visibleChildren++; } } + if (visibleChildren == 0) { + self.addVisibilityOverride(Visibility.OVERRIDE_GONE); + } return changedVisibility; } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java index 47a55b6ed82a..cda90c2d3b0b 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java @@ -138,7 +138,7 @@ public class ColumnLayout extends LayoutManager { for (Component c : mChildrenComponents) { c.measure(context, 0f, maxWidth, 0f, currentMaxHeight, measure); ComponentMeasure m = measure.get(c); - if (m.getVisibility() != Visibility.GONE) { + if (!m.isGone()) { size.setWidth(Math.max(size.getWidth(), m.getW())); size.setHeight(size.getHeight() + m.getH()); visibleChildrens++; @@ -164,7 +164,7 @@ public class ColumnLayout extends LayoutManager { for (Component child : mChildrenComponents) { child.measure(context, minWidth, maxWidth, minHeight, mh, measure); ComponentMeasure m = measure.get(child); - if (m.getVisibility() != Visibility.GONE) { + if (!m.isGone()) { mh -= m.getH(); } } @@ -172,11 +172,11 @@ public class ColumnLayout extends LayoutManager { } @Override - public float intrinsicHeight(@NonNull RemoteContext context) { + public float minIntrinsicHeight(@NonNull RemoteContext context) { float height = computeModifierDefinedHeight(context); float componentHeights = 0f; for (Component c : mChildrenComponents) { - componentHeights += c.intrinsicHeight(context); + componentHeights += c.minIntrinsicHeight(context); } return Math.max(height, componentHeights); } @@ -225,7 +225,7 @@ public class ColumnLayout extends LayoutManager { float totalWeights = 0f; for (Component child : mChildrenComponents) { ComponentMeasure childMeasure = measure.get(child); - if (childMeasure.getVisibility() == Visibility.GONE) { + if (childMeasure.isGone()) { continue; } if (child instanceof LayoutComponent @@ -242,7 +242,7 @@ public class ColumnLayout extends LayoutManager { if (child instanceof LayoutComponent && ((LayoutComponent) child).getHeightModifier().hasWeight()) { ComponentMeasure childMeasure = measure.get(child); - if (childMeasure.getVisibility() == Visibility.GONE) { + if (childMeasure.isGone()) { continue; } float weight = ((LayoutComponent) child).getHeightModifier().getValue(); @@ -280,7 +280,7 @@ public class ColumnLayout extends LayoutManager { int visibleChildrens = 0; for (Component child : mChildrenComponents) { ComponentMeasure childMeasure = measure.get(child); - if (childMeasure.getVisibility() == Visibility.GONE) { + if (childMeasure.isGone()) { continue; } childrenWidth = Math.max(childrenWidth, childMeasure.getW()); @@ -307,17 +307,22 @@ public class ColumnLayout extends LayoutManager { case SPACE_BETWEEN: for (Component child : mChildrenComponents) { ComponentMeasure childMeasure = measure.get(child); - if (childMeasure.getVisibility() == Visibility.GONE) { + if (childMeasure.isGone()) { continue; } total += childMeasure.getH(); } - verticalGap = (selfHeight - total) / (visibleChildrens - 1); + if (visibleChildrens > 1) { + verticalGap = (selfHeight - total) / (visibleChildrens - 1); + } else { + // we center the element + ty = (selfHeight - childrenHeight) / 2f; + } break; case SPACE_EVENLY: for (Component child : mChildrenComponents) { ComponentMeasure childMeasure = measure.get(child); - if (childMeasure.getVisibility() == Visibility.GONE) { + if (childMeasure.isGone()) { continue; } total += childMeasure.getH(); @@ -328,7 +333,7 @@ public class ColumnLayout extends LayoutManager { case SPACE_AROUND: for (Component child : mChildrenComponents) { ComponentMeasure childMeasure = measure.get(child); - if (childMeasure.getVisibility() == Visibility.GONE) { + if (childMeasure.isGone()) { continue; } total += childMeasure.getH(); @@ -353,7 +358,7 @@ public class ColumnLayout extends LayoutManager { } childMeasure.setX(tx); childMeasure.setY(ty); - if (childMeasure.getVisibility() == Visibility.GONE) { + if (childMeasure.isGone()) { continue; } ty += childMeasure.getH(); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/FitBoxLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/FitBoxLayout.java new file mode 100644 index 000000000000..ff7a3af11f20 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/FitBoxLayout.java @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations.layout.managers; + +import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.WireBuffer; +import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; +import com.android.internal.widget.remotecompose.core.operations.layout.Component; +import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent; +import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure; +import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass; +import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightInModifierOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightModifierOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthInModifierOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthModifierOperation; +import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; + +import java.util.List; + +/** FitBox layout implementation -- only display the child that fits in the available space */ +public class FitBoxLayout extends LayoutManager { + + public static final int START = 1; + public static final int CENTER = 2; + public static final int END = 3; + public static final int TOP = 4; + public static final int BOTTOM = 5; + + int mHorizontalPositioning; + int mVerticalPositioning; + + public FitBoxLayout( + @Nullable Component parent, + int componentId, + int animationId, + float x, + float y, + float width, + float height, + int horizontalPositioning, + int verticalPositioning) { + super(parent, componentId, animationId, x, y, width, height); + mHorizontalPositioning = horizontalPositioning; + mVerticalPositioning = verticalPositioning; + } + + public FitBoxLayout( + @Nullable Component parent, + int componentId, + int animationId, + int horizontalPositioning, + int verticalPositioning) { + this( + parent, + componentId, + animationId, + 0, + 0, + 0, + 0, + horizontalPositioning, + verticalPositioning); + } + + @NonNull + @Override + public String toString() { + return "BOX [" + + mComponentId + + ":" + + mAnimationId + + "] (" + + mX + + ", " + + mY + + " - " + + mWidth + + " x " + + mHeight + + ") " + + mVisibility; + } + + @NonNull + @Override + protected String getSerializedName() { + return "FITBOX"; + } + + @Override + public void computeWrapSize( + @NonNull PaintContext context, + float maxWidth, + float maxHeight, + boolean horizontalWrap, + boolean verticalWrap, + @NonNull MeasurePass measure, + @NonNull Size size) { + + boolean found = false; + ComponentMeasure self = measure.get(this); + for (Component c : mChildrenComponents) { + float cw = 0f; // c.intrinsicWidth(context.getContext()); + float ch = 0f; // c.intrinsicHeight(context.getContext()); + if (c instanceof LayoutComponent) { + LayoutComponent lc = (LayoutComponent) c; + WidthModifierOperation widthModifier = lc.getWidthModifier(); + if (widthModifier != null) { + WidthInModifierOperation widthIn = lc.getWidthModifier().getWidthIn(); + if (widthIn != null) { + cw = widthIn.getMin(); + } + } + HeightModifierOperation heightModifier = lc.getHeightModifier(); + if (heightModifier != null) { + HeightInModifierOperation heightIn = lc.getHeightModifier().getHeightIn(); + if (heightIn != null) { + ch = heightIn.getMin(); + } + } + } + c.measure(context, 0f, maxWidth, 0f, maxHeight, measure); + ComponentMeasure m = measure.get(c); + if (!found && cw <= maxWidth && ch <= maxHeight) { + found = true; + m.addVisibilityOverride(Visibility.OVERRIDE_VISIBLE); + size.setWidth(m.getW()); + size.setHeight(m.getH()); + } else { + m.addVisibilityOverride(Visibility.OVERRIDE_GONE); + } + } + if (!found) { + self.setVisibility(Visibility.GONE); + } else { + self.setVisibility(Visibility.VISIBLE); + } + + // add padding + size.setWidth(Math.max(size.getWidth(), computeModifierDefinedWidth(context.getContext()))); + size.setHeight( + Math.max(size.getHeight(), computeModifierDefinedHeight(context.getContext()))); + } + + @Override + public void computeSize( + @NonNull PaintContext context, + float minWidth, + float maxWidth, + float minHeight, + float maxHeight, + @NonNull MeasurePass measure) { + + ComponentMeasure self = measure.get(this); + boolean found = false; + for (Component c : mChildrenComponents) { + float cw = 0f; + float ch = 0f; + if (c instanceof LayoutComponent) { + LayoutComponent lc = (LayoutComponent) c; + WidthModifierOperation widthModifier = lc.getWidthModifier(); + if (widthModifier != null) { + WidthInModifierOperation widthIn = lc.getWidthModifier().getWidthIn(); + if (widthIn != null) { + cw = widthIn.getMin(); + } + } + HeightModifierOperation heightModifier = lc.getHeightModifier(); + if (heightModifier != null) { + HeightInModifierOperation heightIn = lc.getHeightModifier().getHeightIn(); + if (heightIn != null) { + ch = heightIn.getMin(); + } + } + } + c.measure(context, minWidth, maxWidth, minHeight, maxHeight, measure); + // child.measure(context, minWidth, Float.MAX_VALUE, minHeight, + // Float.MAX_VALUE, measure); + // m.getVisibility().clearOverride(); + ComponentMeasure m = measure.get(c); + // m.setVisibility(Visibility.GONE); + // m.getVisibility().add(Visibility.OVERRIDE_GONE); + // m.getVisibility().add(Visibility.OVERRIDE_GONE); + m.clearVisibilityOverride(); + if (!found && cw <= maxWidth && ch <= maxHeight) { + found = true; + m.addVisibilityOverride(Visibility.OVERRIDE_VISIBLE); + } else { + m.addVisibilityOverride(Visibility.OVERRIDE_GONE); + } + } + } + + @Override + public void internalLayoutMeasure(@NonNull PaintContext context, @NonNull MeasurePass measure) { + ComponentMeasure selfMeasure = measure.get(this); + float selfWidth = selfMeasure.getW() - mPaddingLeft - mPaddingRight; + float selfHeight = selfMeasure.getH() - mPaddingTop - mPaddingBottom; + applyVisibility(selfWidth, selfHeight, measure); + for (Component child : mChildrenComponents) { + ComponentMeasure m = measure.get(child); + float tx = 0f; + float ty = 0f; + switch (mVerticalPositioning) { + case TOP: + ty = 0f; + break; + case CENTER: + ty = (selfHeight - m.getH()) / 2f; + break; + case BOTTOM: + ty = selfHeight - m.getH(); + break; + } + switch (mHorizontalPositioning) { + case START: + tx = 0f; + break; + case CENTER: + tx = (selfWidth - m.getW()) / 2f; + break; + case END: + tx = selfWidth - m.getW(); + break; + } + m.setX(tx); + m.setY(ty); + } + } + + /** + * The name of the class + * + * @return the name + */ + @NonNull + public static String name() { + return "BoxLayout"; + } + + /** + * The OP_CODE for this command + * + * @return the opcode + */ + public static int id() { + return Operations.LAYOUT_FIT_BOX; + } + + /** + * Write the operation to the buffer + * + * @param buffer a WireBuffer + * @param componentId the component id + * @param animationId the component animation id + * @param horizontalPositioning the horizontal positioning rules + * @param verticalPositioning the vertical positioning rules + */ + public static void apply( + @NonNull WireBuffer buffer, + int componentId, + int animationId, + int horizontalPositioning, + int verticalPositioning) { + buffer.start(id()); + buffer.writeInt(componentId); + buffer.writeInt(animationId); + buffer.writeInt(horizontalPositioning); + buffer.writeInt(verticalPositioning); + } + + /** + * Read this operation and add it to the list of operations + * + * @param buffer the buffer to read + * @param operations the list of operations that will be added to + */ + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { + int componentId = buffer.readInt(); + int animationId = buffer.readInt(); + int horizontalPositioning = buffer.readInt(); + int verticalPositioning = buffer.readInt(); + operations.add( + new FitBoxLayout( + null, + componentId, + animationId, + horizontalPositioning, + verticalPositioning)); + } + + /** + * Populate the documentation with a description of this operation + * + * @param doc to append the description to. + */ + public static void documentation(@NonNull DocumentationBuilder doc) { + doc.operation("Layout Operations", id(), name()) + .description( + "FitBox layout implementation.\n\n" + + "Only display the first child component that fits in the available" + + " space") + .examplesDimension(150, 100) + .exampleImage("Top", "layout-BoxLayout-start-top.png") + .exampleImage("Center", "layout-BoxLayout-center-center.png") + .exampleImage("Bottom", "layout-BoxLayout-end-bottom.png") + .field(INT, "COMPONENT_ID", "unique id for this component") + .field( + INT, + "ANIMATION_ID", + "id used to match components," + " for animation purposes") + .field(INT, "HORIZONTAL_POSITIONING", "horizontal positioning value") + .possibleValues("START", FitBoxLayout.START) + .possibleValues("CENTER", FitBoxLayout.CENTER) + .possibleValues("END", FitBoxLayout.END) + .field(INT, "VERTICAL_POSITIONING", "vertical positioning value") + .possibleValues("TOP", FitBoxLayout.TOP) + .possibleValues("CENTER", FitBoxLayout.CENTER) + .possibleValues("BOTTOM", FitBoxLayout.BOTTOM); + } + + @Override + public void write(@NonNull WireBuffer buffer) { + apply(buffer, mComponentId, mAnimationId, mHorizontalPositioning, mVerticalPositioning); + } + + @Override + public void serialize(MapSerializer serializer) { + super.serialize(serializer); + serializer.add("verticalPositioning", getPositioningString(mVerticalPositioning)); + serializer.add("horizontalPositioning", getPositioningString(mHorizontalPositioning)); + } + + private String getPositioningString(int pos) { + switch (pos) { + case START: + return "START"; + case CENTER: + return "CENTER"; + case END: + return "END"; + case TOP: + return "TOP"; + case BOTTOM: + return "BOTTOM"; + default: + return "NONE"; + } + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java index 8b52bbe5cdf8..5b66b95cf1dd 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java @@ -73,19 +73,19 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl } @Override - public float intrinsicHeight(@Nullable RemoteContext context) { + public float minIntrinsicHeight(@Nullable RemoteContext context) { float height = computeModifierDefinedHeight(context); for (Component c : mChildrenComponents) { - height = Math.max(c.intrinsicHeight(context), height); + height = Math.max(c.minIntrinsicHeight(context), height); } return height; } @Override - public float intrinsicWidth(@Nullable RemoteContext context) { + public float minIntrinsicWidth(@Nullable RemoteContext context) { float width = computeModifierDefinedWidth(context); for (Component c : mChildrenComponents) { - width = Math.max(c.intrinsicWidth(context), width); + width = Math.max(c.minIntrinsicWidth(context), width); } return width; } @@ -149,10 +149,10 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl Math.min(maxHeight, computeModifierDefinedHeight(context.getContext())); if (mWidthModifier.isIntrinsicMin()) { - maxWidth = intrinsicWidth(context.getContext()) + mPaddingLeft + mPaddingRight; + maxWidth = minIntrinsicWidth(context.getContext()) + mPaddingLeft + mPaddingRight; } if (mHeightModifier.isIntrinsicMin()) { - maxHeight = intrinsicHeight(context.getContext()) + mPaddingTop + mPaddingBottom; + maxHeight = minIntrinsicHeight(context.getContext()) + mPaddingTop + mPaddingBottom; } float insetMaxWidth = maxWidth - mPaddingLeft - mPaddingRight; @@ -171,6 +171,11 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl mHeightModifier.isWrap(), measure, mCachedWrapSize); + int selfVisibilityAfterMeasure = measure.get(this).getVisibility(); + if (Visibility.hasOverride(selfVisibilityAfterMeasure) + && mScheduledVisibility != selfVisibilityAfterMeasure) { + mScheduledVisibility = selfVisibilityAfterMeasure; + } measuredWidth = mCachedWrapSize.getWidth(); if (hasHorizontalWrap) { measuredWidth += mPaddingLeft + mPaddingRight; diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java index e93cbd74b0b5..d5d2e03c3f2a 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java @@ -136,7 +136,7 @@ public class RowLayout extends LayoutManager { for (Component c : mChildrenComponents) { c.measure(context, 0f, currentMaxWidth, 0f, maxHeight, measure); ComponentMeasure m = measure.get(c); - if (m.getVisibility() != Visibility.GONE) { + if (!m.isGone()) { size.setWidth(size.getWidth() + m.getW()); size.setHeight(Math.max(size.getHeight(), m.getH())); visibleChildrens++; @@ -162,7 +162,7 @@ public class RowLayout extends LayoutManager { for (Component child : mChildrenComponents) { child.measure(context, minWidth, mw, minHeight, maxHeight, measure); ComponentMeasure m = measure.get(child); - if (m.getVisibility() != Visibility.GONE) { + if (!m.isGone()) { mw -= m.getW(); } } @@ -170,16 +170,26 @@ public class RowLayout extends LayoutManager { } @Override - public float intrinsicWidth(@Nullable RemoteContext context) { + public float minIntrinsicWidth(@Nullable RemoteContext context) { float width = computeModifierDefinedWidth(context); float componentWidths = 0f; for (Component c : mChildrenComponents) { - componentWidths += c.intrinsicWidth(context); + componentWidths += c.minIntrinsicWidth(context); } return Math.max(width, componentWidths); } @Override + public float minIntrinsicHeight(@Nullable RemoteContext context) { + float height = computeModifierDefinedHeight(context); + float componentHeights = 0f; + for (Component c : mChildrenComponents) { + componentHeights = Math.max(componentHeights, c.minIntrinsicHeight(context)); + } + return Math.max(height, componentHeights); + } + + @Override public void internalLayoutMeasure(@NonNull PaintContext context, @NonNull MeasurePass measure) { ComponentMeasure selfMeasure = measure.get(this); DebugLog.s( @@ -225,7 +235,7 @@ public class RowLayout extends LayoutManager { float totalWeights = 0f; for (Component child : mChildrenComponents) { ComponentMeasure childMeasure = measure.get(child); - if (childMeasure.getVisibility() == Visibility.GONE) { + if (childMeasure.isGone()) { continue; } if (child instanceof LayoutComponent @@ -245,7 +255,7 @@ public class RowLayout extends LayoutManager { if (child instanceof LayoutComponent && ((LayoutComponent) child).getWidthModifier().hasWeight()) { ComponentMeasure childMeasure = measure.get(child); - if (childMeasure.getVisibility() == Visibility.GONE) { + if (childMeasure.isGone()) { continue; } float weight = ((LayoutComponent) child).getWidthModifier().getValue(); @@ -283,7 +293,7 @@ public class RowLayout extends LayoutManager { int visibleChildrens = 0; for (Component child : mChildrenComponents) { ComponentMeasure childMeasure = measure.get(child); - if (childMeasure.getVisibility() == Visibility.GONE) { + if (childMeasure.isGone()) { continue; } childrenWidth += childMeasure.getW(); @@ -311,17 +321,22 @@ public class RowLayout extends LayoutManager { case SPACE_BETWEEN: for (Component child : mChildrenComponents) { ComponentMeasure childMeasure = measure.get(child); - if (childMeasure.getVisibility() == Visibility.GONE) { + if (childMeasure.isGone()) { continue; } total += childMeasure.getW(); } - horizontalGap = (selfWidth - total) / (visibleChildrens - 1); + if (visibleChildrens > 1) { + horizontalGap = (selfWidth - total) / (visibleChildrens - 1); + } else { + // we center the element + tx = (selfWidth - childrenWidth) / 2f; + } break; case SPACE_EVENLY: for (Component child : mChildrenComponents) { ComponentMeasure childMeasure = measure.get(child); - if (childMeasure.getVisibility() == Visibility.GONE) { + if (childMeasure.isGone()) { continue; } total += childMeasure.getW(); @@ -332,7 +347,7 @@ public class RowLayout extends LayoutManager { case SPACE_AROUND: for (Component child : mChildrenComponents) { ComponentMeasure childMeasure = measure.get(child); - if (childMeasure.getVisibility() == Visibility.GONE) { + if (childMeasure.isGone()) { continue; } total += childMeasure.getW(); @@ -357,7 +372,7 @@ public class RowLayout extends LayoutManager { } childMeasure.setX(tx); childMeasure.setY(ty); - if (childMeasure.getVisibility() == Visibility.GONE) { + if (childMeasure.isGone()) { continue; } tx += childMeasure.getW(); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java index ee16bc2f4459..0192d8438a75 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java @@ -413,7 +413,7 @@ public class StateLayout extends LayoutManager { if (index != currentLayoutIndex && index != previousLayoutIndex) { pane.mVisibility = Visibility.GONE; } - if (index == currentLayoutIndex && pane.mVisibility != Visibility.VISIBLE) { + if (index == currentLayoutIndex && !pane.isVisible()) { pane.mVisibility = Visibility.VISIBLE; } index++; @@ -511,7 +511,7 @@ public class StateLayout extends LayoutManager { && previousLayout.mAnimateMeasure == null) { inTransition = false; LayoutManager previous = getLayout(previousLayoutIndex); - if (previous != currentLayout && previous.mVisibility != Visibility.GONE) { + if (previous != currentLayout && !previous.isGone()) { previous.mVisibility = Visibility.GONE; previous.needsRepaint(); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java index d5db74b5ca51..2595a71abaa5 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java @@ -280,7 +280,7 @@ public class TextLayout extends LayoutManager implements VariableSupport, Access + " x " + mHeight + ") " - + mVisibility; + + Visibility.toString(mVisibility); } @NonNull @@ -308,7 +308,7 @@ public class TextLayout extends LayoutManager implements VariableSupport, Access + ", " + mHeight + "] " - + mVisibility + + Visibility.toString(mVisibility) + " (" + mTextId + ":\"" @@ -343,7 +343,7 @@ public class TextLayout extends LayoutManager implements VariableSupport, Access flags |= PaintContext.TEXT_COMPLEX; } context.getTextBounds(mTextId, 0, mCachedString.length(), flags, bounds); - if (bounds[2] - bounds[1] > maxWidth && mMaxLines > 1) { + if (bounds[2] - bounds[1] > maxWidth && mMaxLines > 1 && maxWidth > 0f) { mComputedTextLayout = context.layoutComplexText( mTextId, @@ -375,12 +375,12 @@ public class TextLayout extends LayoutManager implements VariableSupport, Access } @Override - public float intrinsicHeight(@Nullable RemoteContext context) { + public float minIntrinsicHeight(@Nullable RemoteContext context) { return mTextH; } @Override - public float intrinsicWidth(@Nullable RemoteContext context) { + public float minIntrinsicWidth(@Nullable RemoteContext context) { return mTextW; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java index 11ed9f435070..993441944c33 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java @@ -26,7 +26,7 @@ public class ComponentMeasure { float mY; float mW; float mH; - @NonNull Component.Visibility mVisibility = Component.Visibility.VISIBLE; + int mVisibility = Component.Visibility.VISIBLE; public void setX(float value) { mX = value; @@ -60,16 +60,15 @@ public class ComponentMeasure { return mH; } - public @NonNull Component.Visibility getVisibility() { + public int getVisibility() { return mVisibility; } - public void setVisibility(@NonNull Component.Visibility visibility) { + public void setVisibility(int visibility) { mVisibility = visibility; } - public ComponentMeasure( - int id, float x, float y, float w, float h, @NonNull Component.Visibility visibility) { + public ComponentMeasure(int id, float x, float y, float w, float h, int visibility) { this.mId = id; this.mX = x; this.mY = y; @@ -114,4 +113,42 @@ public class ComponentMeasure { public boolean same(@NonNull ComponentMeasure m) { return mX == m.mX && mY == m.mY && mW == m.mW && mH == m.mH && mVisibility == m.mVisibility; } + + /** + * Returns true if the component will be gone + * + * @return true if gone + */ + public boolean isGone() { + return Component.Visibility.isGone(mVisibility); + } + + /** + * Returns true if the component will be visible + * + * @return true if visible + */ + public boolean isVisible() { + return Component.Visibility.isVisible(mVisibility); + } + + /** + * Returns true if the component will be invisible + * + * @return true if invisible + */ + public boolean isInvisible() { + return Component.Visibility.isInvisible(mVisibility); + } + + /** Clear any override on the visibility */ + public void clearVisibilityOverride() { + mVisibility = Component.Visibility.clearOverride(mVisibility); + } + + /** Add a visibility override */ + public void addVisibilityOverride(int value) { + mVisibility = Component.Visibility.clearOverride(mVisibility); + mVisibility = Component.Visibility.add(mVisibility, value); + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java index fd5f8c9cdbe7..1ab0c3dc7fb8 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java @@ -228,7 +228,7 @@ public class BackgroundModifierOperation extends DecoratorModifierOperation { public void serialize(MapSerializer serializer) { serializer .addTags(SerializeTags.MODIFIER) - .add("type", "BackgroundModifierOperation") + .addType("BackgroundModifierOperation") .add("x", mX) .add("y", mY) .add("width", mWidth) diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java index e5f318307a75..656a3c0fca68 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java @@ -290,7 +290,7 @@ public class BorderModifierOperation extends DecoratorModifierOperation { public void serialize(MapSerializer serializer) { serializer .addTags(SerializeTags.MODIFIER) - .add("type", "BorderModifierOperation") + .addType("BorderModifierOperation") .add("x", mX) .add("y", mY) .add("width", mWidth) diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java index 00a53170fc48..e96dc838fc91 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java @@ -111,7 +111,7 @@ public class ClipRectModifierOperation extends DecoratorModifierOperation { public void serialize(MapSerializer serializer) { serializer .addTags(SerializeTags.MODIFIER) - .add("type", "ClipRectModifierOperation") + .addType("ClipRectModifierOperation") .add("width", mWidth) .add("height", mHeight); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java index a9e342144cc5..14b2fad5825d 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java @@ -327,7 +327,7 @@ public class ComponentModifiers extends PaintOperation public void serialize(MapSerializer serializer) { serializer .addTags(SerializeTags.MODIFIER) - .add("type", "ComponentModifiers") + .addType("ComponentModifiers") .add("modifiers", mList); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java index fbf8a95248b9..88b28c39eec6 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java @@ -41,7 +41,7 @@ public class ComponentVisibilityOperation extends Operation private static final int OP_CODE = Operations.MODIFIER_VISIBILITY; int mVisibilityId; - @NonNull Component.Visibility mVisibility = Component.Visibility.VISIBLE; + int mVisibility = Component.Visibility.VISIBLE; private LayoutComponent mParent; public ComponentVisibilityOperation(int id) { @@ -124,11 +124,11 @@ public class ComponentVisibilityOperation extends Operation @Override public void updateVariables(@NonNull RemoteContext context) { int visibility = context.getInteger(mVisibilityId); - if (visibility == Component.Visibility.VISIBLE.ordinal()) { + if (Component.Visibility.isVisible(visibility)) { mVisibility = Component.Visibility.VISIBLE; - } else if (visibility == Component.Visibility.GONE.ordinal()) { + } else if (Component.Visibility.isGone(visibility)) { mVisibility = Component.Visibility.GONE; - } else if (visibility == Component.Visibility.INVISIBLE.ordinal()) { + } else if (Component.Visibility.isInvisible(visibility)) { mVisibility = Component.Visibility.INVISIBLE; } else { mVisibility = Component.Visibility.GONE; @@ -150,8 +150,8 @@ public class ComponentVisibilityOperation extends Operation public void serialize(MapSerializer serializer) { serializer .addTags(SerializeTags.MODIFIER) - .add("type", "ComponentVisibilityOperation") + .addType("ComponentVisibilityOperation") .add("visibilityId", mVisibilityId) - .add("visibility", mVisibility); + .add("visibility", Component.Visibility.toString(mVisibility)); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DrawContentOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DrawContentOperation.java index d7abdbae4962..6beb13586163 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DrawContentOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DrawContentOperation.java @@ -120,6 +120,6 @@ public class DrawContentOperation extends Operation @Override public void serialize(MapSerializer serializer) { - serializer.addTags(SerializeTags.MODIFIER).add("type", "DrawContentOperation"); + serializer.addTags(SerializeTags.MODIFIER).addType("DrawContentOperation"); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java index c1c1f9568ea8..361438b51a47 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java @@ -352,7 +352,7 @@ public class GraphicsLayerModifierOperation extends DecoratorModifierOperation { public void serialize(MapSerializer serializer) { serializer .addTags(SerializeTags.MODIFIER) - .add("type", "GraphicsLayerModifierOperation") + .addType("GraphicsLayerModifierOperation") .add("scaleX", mScaleX) .add("scaleX", mScaleX) .add("rotationX", mRotationX) diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java index 7f0dd8d8d8e5..9b63c034675a 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java @@ -107,7 +107,7 @@ public class HeightInModifierOperation extends DimensionInModifierOperation { public void serialize(MapSerializer serializer) { serializer .addTags(SerializeTags.MODIFIER) - .add("type", "HeightInModifierOperation") + .addType("HeightInModifierOperation") .add("min", mV1, mValue1) .add("max", mV2, mValue2); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java index 1df84257f344..5fbaafca5759 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java @@ -143,7 +143,7 @@ public class HeightModifierOperation extends DimensionModifierOperation { public void serialize(MapSerializer serializer) { serializer .addTags(SerializeTags.MODIFIER) - .add("type", "HeightModifierOperation") + .addType("HeightModifierOperation") .add("height", mValue, mOutValue) .add("dimensionModifierType", mType); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java index 67714ef5abac..4d8acb4f04e2 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java @@ -130,7 +130,7 @@ public class HostActionOperation extends Operation public void serialize(MapSerializer serializer) { serializer .addTags(SerializeTags.MODIFIER) - .add("type", "HostActionOperation") + .addType("HostActionOperation") .add("id", mActionId); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java index 40c13f143468..807ff68fa965 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java @@ -157,7 +157,7 @@ public class HostNamedActionOperation extends Operation implements ActionOperati public void serialize(MapSerializer serializer) { serializer .addTags(SerializeTags.MODIFIER) - .add("type", "HostNamedActionOperation") + .addType("HostNamedActionOperation") .add("textId", mTextId) .add("actionType", getActionType(mType)) .add("valueId", mValueId); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java index d2a1684a262a..c493e253d243 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java @@ -247,8 +247,8 @@ public class MarqueeModifierOperation extends DecoratorModifierOperation impleme mComponentHeight = height; if (component instanceof LayoutComponent) { LayoutComponent layoutComponent = (LayoutComponent) component; - setContentWidth(layoutComponent.intrinsicWidth(context)); - setContentHeight(layoutComponent.intrinsicHeight(context)); + setContentWidth(layoutComponent.minIntrinsicWidth(context)); + setContentHeight(layoutComponent.minIntrinsicHeight(context)); } } @@ -256,7 +256,7 @@ public class MarqueeModifierOperation extends DecoratorModifierOperation impleme public void serialize(MapSerializer serializer) { serializer .addTags(SerializeTags.MODIFIER) - .add("type", "MarqueeModifierOperation") + .addType("MarqueeModifierOperation") .add("iterations", mIterations) .add("animationMode", mAnimationMode) .add("repeatDelayMillis", mRepeatDelayMillis) diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java index b7fe97b0e936..37f56cbc02e1 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java @@ -162,7 +162,7 @@ public class OffsetModifierOperation extends DecoratorModifierOperation { public void serialize(MapSerializer serializer) { serializer .addTags(SerializeTags.MODIFIER) - .add("type", "OffsetModifierOperation") + .addType("OffsetModifierOperation") .add("x", mX) .add("y", mY); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java index d5b3a0b09261..0156992650ed 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java @@ -184,7 +184,7 @@ public class PaddingModifierOperation extends Operation implements ModifierOpera public void serialize(MapSerializer serializer) { serializer .addTags(SerializeTags.MODIFIER) - .add("type", "PaddingModifierOperation") + .addType("PaddingModifierOperation") .add("left", mLeft) .add("top", mTop) .add("right", mRight) diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java index 69ace8478e08..eb5bfcff7d40 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java @@ -217,7 +217,7 @@ public class RippleModifierOperation extends DecoratorModifierOperation implemen public void serialize(MapSerializer serializer) { serializer .addTags(SerializeTags.MODIFIER) - .add("type", "RippleModifierOperation") + .addType("RippleModifierOperation") .add("animateRippleStart", mAnimateRippleStart) .add("animateRippleX", mAnimateRippleX) .add("animateRippleY", mAnimateRippleY) diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java index 8442e05e59df..f0ed905ac158 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java @@ -167,7 +167,7 @@ public class RoundedClipRectModifierOperation extends DrawBase4 public void serialize(MapSerializer serializer) { serialize(serializer, "topStart", "topEnd", "bottomStart", "bottomEnd") .addTags(SerializeTags.MODIFIER) - .add("type", CLASS_NAME) + .addType(CLASS_NAME) .add("width", mWidth) .add("height", mHeight); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java index a57365ec9132..466e435e20cf 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java @@ -397,7 +397,7 @@ public class ScrollModifierOperation extends ListActionsOperation public void serialize(MapSerializer serializer) { serializer .addTags(SerializeTags.MODIFIER) - .add("type", "ScrollModifierOperation") + .addType("ScrollModifierOperation") .add("direction", mDirection) .add("max", mMax) .add("notchMax", mNotchMax) diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java index bd91734602d9..171e2bed6b00 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java @@ -126,7 +126,7 @@ public class ValueFloatChangeActionOperation extends Operation implements Action public void serialize(MapSerializer serializer) { serializer .addTags(SerializeTags.MODIFIER, SerializeTags.ACTION) - .add("type", "ValueFloatChangeActionOperation") + .addType("ValueFloatChangeActionOperation") .add("targetValueId", mTargetValueId) .add("value", mValue); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java index 4b18d0aaf0df..d8133f6715a8 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java @@ -133,7 +133,7 @@ public class ValueFloatExpressionChangeActionOperation extends Operation public void serialize(MapSerializer serializer) { serializer .addTags(SerializeTags.MODIFIER, SerializeTags.ACTION) - .add("type", "ValueFloatExpressionChangeActionOperation") + .addType("ValueFloatExpressionChangeActionOperation") .add("targetValueId", mTargetValueId) .add("valueExpressionId", mValueExpressionId); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java index d86c4a6ad416..05a6fd0f10da 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java @@ -131,7 +131,7 @@ public class ValueIntegerChangeActionOperation extends Operation implements Acti public void serialize(MapSerializer serializer) { serializer .addTags(SerializeTags.MODIFIER, SerializeTags.ACTION) - .add("type", "ValueIntegerChangeActionOperation") + .addType("ValueIntegerChangeActionOperation") .add("targetValueId", mTargetValueId) .add("value", mValue); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java index e253460d2afb..8994febe7f7c 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java @@ -133,7 +133,7 @@ public class ValueIntegerExpressionChangeActionOperation extends Operation public void serialize(MapSerializer serializer) { serializer .addTags(SerializeTags.MODIFIER, SerializeTags.ACTION) - .add("type", "ValueIntegerExpressionChangeActionOperation") + .addType("ValueIntegerExpressionChangeActionOperation") .add("targetValueId", mTargetValueId) .add("valueExpressionId", mValueExpressionId); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java index e84b29922523..08960d3576f4 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java @@ -139,7 +139,7 @@ public class ValueStringChangeActionOperation extends Operation implements Actio public void serialize(MapSerializer serializer) { serializer .addTags(SerializeTags.MODIFIER, SerializeTags.ACTION) - .add("type", "ValueIntegerExpressionChangeActionOperation") + .addType("ValueIntegerExpressionChangeActionOperation") .add("targetValueId", mTargetValueId) .add("valueId", mValueId); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java index 3282a9cb5426..93074c79319f 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java @@ -107,7 +107,7 @@ public class WidthInModifierOperation extends DimensionInModifierOperation { public void serialize(MapSerializer serializer) { serializer .addTags(SerializeTags.MODIFIER) - .add("type", "WidthInModifierOperation") + .addType("WidthInModifierOperation") .add("min", mV1, mValue1) .add("max", mV2, mValue2); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java index 6fe5a70b7ad4..ebdafa2aaa89 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java @@ -143,7 +143,7 @@ public class WidthModifierOperation extends DimensionModifierOperation { public void serialize(MapSerializer serializer) { serializer .addTags(SerializeTags.MODIFIER) - .add("type", "WidthModifierOperation") + .addType("WidthModifierOperation") .add("width", mValue, mOutValue) .add("dimensionModifierType", mType); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java index f250951ab87e..ddb34b57e936 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java @@ -147,7 +147,7 @@ public class ZIndexModifierOperation extends DecoratorModifierOperation { public void serialize(MapSerializer serializer) { serializer .addTags(SerializeTags.MODIFIER) - .add("type", "ZIndexModifierOperation") + .addType("ZIndexModifierOperation") .add("value", mValue); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java index 0f17b114133d..55b64364e4e5 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java @@ -1248,7 +1248,7 @@ public class PaintBundle implements Serializable { @Override public void serialize(MapSerializer serializer) { - serializer.add("type", "PaintBundle"); + serializer.addType("PaintBundle"); List<Map<String, Object>> list = new ArrayList<>(); int i = 0; while (i < mPos) { @@ -1336,6 +1336,7 @@ public class PaintBundle implements Serializable { serializer.add("operations", list); } + @SuppressWarnings("JdkImmutableCollections") private static Map<String, Object> getVariable(int value) { float fValue = Float.intBitsToFloat(value); if (Float.isNaN(fValue)) { @@ -1344,6 +1345,7 @@ public class PaintBundle implements Serializable { return orderedOf("type", "Value", "value", fValue); } + @SuppressWarnings("JdkImmutableCollections") private static int serializeGradient( int cmd, int[] array, int i, List<Map<String, Object>> list) { int ret = i; diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java index cad76059f7a4..349ab6117b2b 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java @@ -34,8 +34,10 @@ public class FloatAnimation extends Easing implements Serializable { private float mWrap = Float.NaN; private float mInitialValue = Float.NaN; private float mTargetValue = Float.NaN; + private int mDirectionalSnap = 0; // private float mScale = 1; float mOffset = 0; + private boolean mPropagate = false; @NonNull @Override @@ -161,11 +163,15 @@ public class FloatAnimation extends Easing implements Serializable { int type = 0; float wrapValue = Float.NaN; float initialValue = Float.NaN; + int directionalSnap = 0; + boolean propagate = false; if (mSpec.length > 1) { int num_type = Float.floatToRawIntBits(mSpec[1]); type = num_type & 0xFF; boolean wrap = ((num_type >> 8) & 0x1) > 0; boolean init = ((num_type >> 8) & 0x2) > 0; + directionalSnap = (num_type >> 10) & 0x3; + propagate = ((num_type >> 12) & 0x1) > 0; len = (num_type >> 16) & 0xFFFF; int off = 2 + len; if (init) { @@ -229,6 +235,12 @@ public class FloatAnimation extends Easing implements Serializable { if (!Float.isNaN(wrapValue)) { str += " wrap =" + wrapValue; } + if (directionalSnap != 0) { + str += " directionalSnap=" + directionalSnap; + } + if (propagate) { + str += " propagate"; + } return str; } @@ -246,6 +258,8 @@ public class FloatAnimation extends Easing implements Serializable { mType = num_type & 0xFF; boolean wrap = ((num_type >> 8) & 0x1) > 0; boolean init = ((num_type >> 8) & 0x2) > 0; + int directional = (num_type >> 10) & 0x3; + boolean propagate = ((num_type >> 12) & 0x1) > 0; len = (num_type >> 16) & 0xFFFF; int off = 2 + len; if (init) { @@ -254,6 +268,8 @@ public class FloatAnimation extends Easing implements Serializable { if (wrap) { mWrap = mSpec[off]; } + mDirectionalSnap = directional; + mPropagate = propagate; } create(mType, description, 2, len); } @@ -347,7 +363,13 @@ public class FloatAnimation extends Easing implements Serializable { float dist = wrapDistance(mWrap, mInitialValue, mTargetValue); if ((dist > 0) && (mTargetValue < mInitialValue)) { mTargetValue += mWrap; - } else if ((dist < 0) && (mTargetValue > mInitialValue)) { + } else if ((dist < 0) && mDirectionalSnap != 0) { + if (mDirectionalSnap == 1 && mTargetValue > mInitialValue) { + mInitialValue = mTargetValue; + } + if (mDirectionalSnap == 2 && mTargetValue < mInitialValue) { + mInitialValue = mTargetValue; + } mTargetValue -= mWrap; } } @@ -377,6 +399,14 @@ public class FloatAnimation extends Easing implements Serializable { /** get the value at time t in seconds since start */ @Override public float get(float t) { + if (mDirectionalSnap == 1 && mTargetValue < mInitialValue) { + mInitialValue = mTargetValue; + return mTargetValue; + } + if (mDirectionalSnap == 2 && mTargetValue > mInitialValue) { + mInitialValue = mTargetValue; + return mTargetValue; + } return mEasingCurve.get(t / mDuration) * (mTargetValue - mInitialValue) + mInitialValue; } @@ -387,6 +417,13 @@ public class FloatAnimation extends Easing implements Serializable { } /** + * @return if you should propagate the animation + */ + public boolean isPropagate() { + return mPropagate; + } + + /** * Get the initial value * * @return the initial value @@ -398,7 +435,7 @@ public class FloatAnimation extends Easing implements Serializable { @Override public void serialize(MapSerializer serializer) { serializer - .add("type", "FloatAnimation") + .addType("FloatAnimation") .add("initialValue", mInitialValue) .add("targetValue", mInitialValue) .add("duration", mInitialValue) diff --git a/core/java/com/android/internal/widget/remotecompose/core/semantics/CoreSemantics.java b/core/java/com/android/internal/widget/remotecompose/core/semantics/CoreSemantics.java index 08559fc21acb..424894a3e665 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/semantics/CoreSemantics.java +++ b/core/java/com/android/internal/widget/remotecompose/core/semantics/CoreSemantics.java @@ -161,7 +161,7 @@ public class CoreSemantics extends Operation implements AccessibilityModifier { public void serialize(MapSerializer serializer) { serializer .addTags(SerializeTags.MODIFIER, SerializeTags.A11Y) - .add("type", "CoreSemantics") + .addType("CoreSemantics") .add("contentDescriptionId", mContentDescriptionId) .add("role", mRole) .add("textId", mTextId) diff --git a/core/java/com/android/internal/widget/remotecompose/core/serialize/MapSerializer.java b/core/java/com/android/internal/widget/remotecompose/core/serialize/MapSerializer.java index f9ecf0f4f672..20e94ab5d898 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/serialize/MapSerializer.java +++ b/core/java/com/android/internal/widget/remotecompose/core/serialize/MapSerializer.java @@ -25,11 +25,17 @@ import java.util.Map; public interface MapSerializer { /** + * Adds a "type" field with this value + * + * @param type The name of the type + */ + MapSerializer addType(String type); + + /** * Add a float expression * - * @param key - * @param value - * @return + * @param key The key + * @param value The float src */ MapSerializer addFloatExpressionSrc(String key, float[] value); diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java index cb759a61249a..0da543f3336c 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java +++ b/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java @@ -131,6 +131,6 @@ public class BooleanConstant extends Operation implements Serializable { @Override public void serialize(MapSerializer serializer) { - serializer.add("type", CLASS_NAME).add("id", mId).add("value", mValue); + serializer.addType(CLASS_NAME).add("id", mId).add("value", mValue); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java index c734f813ede3..bdc765968387 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java +++ b/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java @@ -123,6 +123,6 @@ public class IntegerConstant extends Operation implements Serializable { @Override public void serialize(MapSerializer serializer) { - serializer.add("type", CLASS_NAME).add("id", mId).add("value", mValue); + serializer.addType(CLASS_NAME).add("id", mId).add("value", mValue); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java index 50509f3636b3..d071e0a21d22 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java +++ b/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java @@ -35,9 +35,13 @@ public class LongConstant extends Operation implements Serializable { private static final String CLASS_NAME = "LongConstant"; private static final int OP_CODE = Operations.DATA_LONG; - private final long mValue; + private long mValue; private final int mId; + /** + * @param id the id of the constant + * @param value the value of the constant + */ public LongConstant(int id, long value) { mId = id; mValue = value; @@ -52,6 +56,15 @@ public class LongConstant extends Operation implements Serializable { return mValue; } + /** + * Set the value of the long constant + * + * @param value the value to set it to + */ + public void setValue(long value) { + mValue = value; + } + @Override public void write(@NonNull WireBuffer buffer) { apply(buffer, mId, mValue); @@ -114,6 +127,6 @@ public class LongConstant extends Operation implements Serializable { @Override public void serialize(MapSerializer serializer) { - serializer.add("type", CLASS_NAME).add("id", mId).add("value", mValue); + serializer.addType(CLASS_NAME).add("id", mId).add("value", mValue); } } diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java index 1d1e579ebc2f..1f9a27429067 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java +++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java @@ -388,6 +388,16 @@ public class RemoteComposePlayer extends FrameLayout implements RemoteContextAwa mInner.setColor(colorName, colorValue); } + /** + * This sets long based on its name. + * + * @param name Name of the color + * @param value The new long value + */ + public void setLong(String name, long value) { + mInner.setLong(name, value); + } + private void mapColors() { String[] name = getNamedColors(); diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java index ac4a294b5e5e..b5aedd8d0231 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java +++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java @@ -290,8 +290,8 @@ public class AndroidPaintContext extends PaintContext { } if ((flags & PaintContext.TEXT_MEASURE_FONT_HEIGHT) != 0) { - bounds[1] = Math.round(mCachedFontMetrics.top); - bounds[3] = Math.round(mCachedFontMetrics.bottom); + bounds[1] = Math.round(mCachedFontMetrics.ascent); + bounds[3] = Math.round(mCachedFontMetrics.descent); } else { bounds[1] = mTmpRect.top; bounds[3] = mTmpRect.bottom; @@ -344,6 +344,7 @@ public class AndroidPaintContext extends PaintContext { default: } staticLayoutBuilder.setMaxLines(maxLines); + staticLayoutBuilder.setIncludePad(false); StaticLayout staticLayout = staticLayoutBuilder.build(); return new AndroidComputedTextLayout( diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java index ba8d83bff51f..51c42fe111bd 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java +++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java @@ -60,6 +60,14 @@ public class AndroidPlatformServices implements Platform { } @Override + public boolean isAlpha8Image(@NonNull Object image) { + if (image instanceof Bitmap) { + return ((Bitmap) image).getConfig().equals(Bitmap.Config.ALPHA_8); + } + return false; + } + + @Override @Nullable public float[] pathToFloatArray(@NonNull Object path) { // if (path is RemotePath) { diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java index 14349b028819..b31c76056e38 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java +++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java @@ -20,6 +20,7 @@ import android.annotation.Nullable; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; +import android.graphics.Paint; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -30,6 +31,7 @@ import com.android.internal.widget.remotecompose.core.operations.FloatExpression import com.android.internal.widget.remotecompose.core.operations.ShaderData; import com.android.internal.widget.remotecompose.core.operations.utilities.ArrayAccess; import com.android.internal.widget.remotecompose.core.operations.utilities.DataMap; +import com.android.internal.widget.remotecompose.core.types.LongConstant; import java.io.IOException; import java.net.MalformedURLException; @@ -141,6 +143,16 @@ public class AndroidRemoteContext extends RemoteContext { } @Override + public void setNamedLong(String name, long value) { + VarName entry = mVarNameHashMap.get(name); + if (entry != null) { + int id = entry.mId; + LongConstant longConstant = (LongConstant) mRemoteComposeState.getObject(id); + longConstant.setValue(value); + } + } + + @Override public void setNamedDataOverride(String dataName, Object value) { if (mVarNameHashMap.get(dataName) != null) { int id = mVarNameHashMap.get(dataName).mId; @@ -215,6 +227,27 @@ public class AndroidRemoteContext extends RemoteContext { case BitmapData.TYPE_PNG_8888: image = BitmapFactory.decodeByteArray(data, 0, data.length); break; + case BitmapData.TYPE_PNG_ALPHA_8: + image = decodePreferringAlpha8(data); + + // If needed convert to ALPHA_8. + if (!image.getConfig().equals(Bitmap.Config.ALPHA_8)) { + Bitmap alpha8Bitmap = + Bitmap.createBitmap( + image.getWidth(), + image.getHeight(), + Bitmap.Config.ALPHA_8); + Canvas canvas = new Canvas(alpha8Bitmap); + Paint paint = new Paint(); + paint.setXfermode( + new android.graphics.PorterDuffXfermode( + android.graphics.PorterDuff.Mode.SRC)); + canvas.drawBitmap(image, 0, 0, paint); + image.recycle(); // Release resources + + image = alpha8Bitmap; + } + break; case BitmapData.TYPE_RAW8888: image = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); int[] idata = new int[data.length / 4]; @@ -255,6 +288,12 @@ public class AndroidRemoteContext extends RemoteContext { } } + private Bitmap decodePreferringAlpha8(@NonNull byte[] data) { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inPreferredConfig = Bitmap.Config.ALPHA_8; + return BitmapFactory.decodeByteArray(data, 0, data.length, options); + } + @Override public void loadText(int id, @NonNull String text) { if (!mRemoteComposeState.containsId(id)) { diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java index 4d2dd05ca603..29cd40def562 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java +++ b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java @@ -200,6 +200,16 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta mARContext.setNamedColorOverride(colorName, colorValue); } + /** + * set the value of a long associated with this name. + * + * @param name Name of color typically "android.xxx" + * @param value the long value + */ + public void setLong(String name, long value) { + mARContext.setNamedLong(name, value); + } + public RemoteComposeDocument getDocument() { return mDocument; } diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto index 34ec1481f697..8de77469d170 100644 --- a/core/proto/android/providers/settings/secure.proto +++ b/core/proto/android/providers/settings/secure.proto @@ -111,6 +111,7 @@ message SecureSettingsProto { optional SettingProto autoclick_cursor_area_size = 62 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto autoclick_ignore_minor_cursor_movement = 63 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto autoclick_panel_position = 64 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto autoclick_revert_to_left_click = 65 [ (android.privacy).dest = DEST_AUTOMATIC ]; } optional Accessibility accessibility = 2; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ee6899cf866b..e16ce9849ff2 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -8995,13 +8995,13 @@ <!-- @SystemApi @FlaggedApi("android.permission.flags.text_classifier_choice_api_enabled") - This permission is required to access the specific text classifier you need from the + This permission is required to access the specific text classifier from the TextClassificationManager. - <p>Protection level: signature|role + <p>Protection level: signature|role|privileged @hide --> <permission android:name="android.permission.ACCESS_TEXT_CLASSIFIER_BY_TYPE" - android:protectionLevel="signature|role" + android:protectionLevel="signature|role|privileged" android:featureFlag="android.permission.flags.text_classifier_choice_api_enabled"/> <!-- Attribution for Geofencing service. --> diff --git a/core/res/res/layout/accessibility_autoclick_type_panel.xml b/core/res/res/layout/accessibility_autoclick_type_panel.xml index cedbdc175488..902ef7fc38e8 100644 --- a/core/res/res/layout/accessibility_autoclick_type_panel.xml +++ b/core/res/res/layout/accessibility_autoclick_type_panel.xml @@ -17,7 +17,7 @@ */ --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +<com.android.server.accessibility.autoclick.AutoclickLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/accessibility_autoclick_type_panel" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -130,4 +130,4 @@ </LinearLayout> -</LinearLayout> +</com.android.server.accessibility.autoclick.AutoclickLinearLayout> diff --git a/core/tests/coretests/src/android/os/PerfettoTraceTest.java b/core/tests/coretests/src/android/os/PerfettoTraceTest.java index 69150150d6f9..790ac4a55dc6 100644 --- a/core/tests/coretests/src/android/os/PerfettoTraceTest.java +++ b/core/tests/coretests/src/android/os/PerfettoTraceTest.java @@ -33,6 +33,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -611,6 +612,7 @@ public class PerfettoTraceTest { @Test @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2) + @Ignore("b/303199244") public void testMessageQueue() throws Exception { TraceConfig traceConfig = getTraceConfig("mq"); diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 15f70298198f..9234902335c1 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -619,6 +619,8 @@ applications that come with the platform <permission name="android.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE"/> <permission name="android.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE"/> <permission name="android.permission.READ_COLOR_ZONES"/> + <!-- Permission required for CTS test - CtsTextClassifierTestCases --> + <permission name="android.permission.ACCESS_TEXT_CLASSIFIER_BY_TYPE"/> </privapp-permissions> <privapp-permissions package="com.android.statementservice"> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java index b507ca2019a9..3f21e74a7d03 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java @@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import android.app.ActivityManager; +import android.window.DesktopExperienceFlags; import android.window.DisplayAreaInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; @@ -67,7 +68,7 @@ public class PipDesktopState { /** Returns whether PiP in Connected Displays is enabled by checking the flag. */ public boolean isConnectedDisplaysPipEnabled() { - return Flags.enableConnectedDisplaysPip(); + return DesktopExperienceFlags.ENABLE_CONNECTED_DISPLAYS_PIP.isTrue(); } /** Returns whether the display with the PiP task is in freeform windowing mode. */ 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 5de3be4bbfc0..8f7e52ea2108 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 @@ -677,11 +677,7 @@ class DesktopTasksController( // Bring other apps to front first. bringDesktopAppsToFrontBeforeShowingNewTask(displayId, wct, task.taskId) } - if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { - prepareMoveTaskToDesk(wct, task, deskId) - } else { - addMoveToDesktopChanges(wct, task) - } + addMoveToDeskTaskChanges(wct = wct, task = task, deskId = deskId) return taskIdToMinimize } @@ -1260,6 +1256,8 @@ class DesktopTasksController( * Move [task] to display with [displayId]. * * No-op if task is already on that display per [RunningTaskInfo.displayId]. + * + * TODO: b/399411604 - split this up into smaller functions. */ private fun moveToDisplay(task: RunningTaskInfo, displayId: Int) { logV("moveToDisplay: taskId=%d displayId=%d", task.taskId, displayId) @@ -1315,16 +1313,20 @@ class DesktopTasksController( // TODO: b/393977830 and b/397437641 - do not assume that freeform==desktop. if (!task.isFreeform) { - addMoveToDesktopChanges(wct, task, displayId) - } else if (Flags.enableMoveToNextDisplayShortcut()) { - applyFreeformDisplayChange(wct, task, displayId) + addMoveToDeskTaskChanges(wct = wct, task = task, deskId = destinationDeskId) + } else { + if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { + desksOrganizer.moveTaskToDesk(wct, destinationDeskId, task) + } + if (Flags.enableMoveToNextDisplayShortcut()) { + applyFreeformDisplayChange(wct, task, displayId) + } } - if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { - desksOrganizer.moveTaskToDesk(wct, destinationDeskId, task) - } else { + if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { wct.reparent(task.token, displayAreaInfo.token, /* onTop= */ true) } + addDeskActivationChanges(destinationDeskId, wct) val activationRunnable: RunOnTransitStart = { transition -> desksTransitionObserver.addPendingTransition( @@ -2062,12 +2064,13 @@ class DesktopTasksController( triggerTask?.let { task -> when { // Check if freeform task launch during recents should be handled - shouldHandleMidRecentsFreeformLaunch -> handleMidRecentsFreeformTaskLaunch(task) + shouldHandleMidRecentsFreeformLaunch -> + handleMidRecentsFreeformTaskLaunch(task, transition) // Check if the closing task needs to be handled TransitionUtil.isClosingType(request.type) -> handleTaskClosing(task, transition, request.type) // Check if the top task shouldn't be allowed to enter desktop mode - isIncompatibleTask(task) -> handleIncompatibleTaskLaunch(task) + isIncompatibleTask(task) -> handleIncompatibleTaskLaunch(task, transition) // Check if fullscreen task should be updated task.isFullscreen -> handleFullscreenTaskLaunch(task, transition) // Check if freeform task should be updated @@ -2306,20 +2309,23 @@ class DesktopTasksController( * This is a special case where we want to launch the task in fullscreen instead of freeform. */ private fun handleMidRecentsFreeformTaskLaunch( - task: RunningTaskInfo + task: RunningTaskInfo, + transition: IBinder, ): WindowContainerTransaction? { logV("DesktopTasksController: handleMidRecentsFreeformTaskLaunch") val wct = WindowContainerTransaction() - addMoveToFullscreenChanges( - wct = wct, - taskInfo = task, - willExitDesktop = - willExitDesktop( - triggerTaskId = task.taskId, - displayId = task.displayId, - forceExitDesktop = true, - ), - ) + val runOnTransitStart = + addMoveToFullscreenChanges( + wct = wct, + taskInfo = task, + willExitDesktop = + willExitDesktop( + triggerTaskId = task.taskId, + displayId = task.displayId, + forceExitDesktop = true, + ), + ) + runOnTransitStart?.invoke(transition) wct.reorder(task.token, true) return wct } @@ -2343,16 +2349,18 @@ class DesktopTasksController( // launched. We should make this task go to fullscreen instead of freeform. Note // that this means any re-launch of a freeform window outside of desktop will be in // fullscreen as long as default-desktop flag is disabled. - addMoveToFullscreenChanges( - wct = wct, - taskInfo = task, - willExitDesktop = - willExitDesktop( - triggerTaskId = task.taskId, - displayId = task.displayId, - forceExitDesktop = true, - ), - ) + val runOnTransitStart = + addMoveToFullscreenChanges( + wct = wct, + taskInfo = task, + willExitDesktop = + willExitDesktop( + triggerTaskId = task.taskId, + displayId = task.displayId, + forceExitDesktop = true, + ), + ) + runOnTransitStart?.invoke(transition) return wct } bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId) @@ -2416,7 +2424,8 @@ class DesktopTasksController( if (shouldFullscreenTaskLaunchSwitchToDesktop(task)) { logD("Switch fullscreen task to freeform on transition: taskId=%d", task.taskId) return WindowContainerTransaction().also { wct -> - addMoveToDesktopChanges(wct, task) + val deskId = getDefaultDeskId(task.displayId) + addMoveToDeskTaskChanges(wct = wct, task = task, deskId = deskId) // In some launches home task is moved behind new task being launched. Make sure // that's not the case for launches in desktop. Also, if this launch is the first // one to trigger the desktop mode (e.g., when [forceEnterDesktop()]), activate the @@ -2447,7 +2456,8 @@ class DesktopTasksController( // If a freeform task receives a request for a fullscreen launch, apply the same // changes we do for similar transitions. The task not having WINDOWING_MODE_UNDEFINED // set when needed can interfere with future split / multi-instance transitions. - return WindowContainerTransaction().also { wct -> + val wct = WindowContainerTransaction() + val runOnTransitStart = addMoveToFullscreenChanges( wct = wct, taskInfo = task, @@ -2458,7 +2468,8 @@ class DesktopTasksController( forceExitDesktop = true, ), ) - } + runOnTransitStart?.invoke(transition) + return wct } return null } @@ -2473,7 +2484,10 @@ class DesktopTasksController( * If a task is not compatible with desktop mode freeform, it should always be launched in * fullscreen. */ - private fun handleIncompatibleTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? { + private fun handleIncompatibleTaskLaunch( + task: RunningTaskInfo, + transition: IBinder, + ): WindowContainerTransaction? { logV("handleIncompatibleTaskLaunch") if (!isDesktopModeShowing(task.displayId) && !forceEnterDesktop(task.displayId)) return null // Only update task repository for transparent task. @@ -2485,7 +2499,8 @@ class DesktopTasksController( } // Already fullscreen, no-op. if (task.isFullscreen) return null - return WindowContainerTransaction().also { wct -> + val wct = WindowContainerTransaction() + val runOnTransitStart = addMoveToFullscreenChanges( wct = wct, taskInfo = task, @@ -2496,7 +2511,8 @@ class DesktopTasksController( forceExitDesktop = true, ), ) - } + runOnTransitStart?.invoke(transition) + return wct } /** @@ -2543,55 +2559,44 @@ class DesktopTasksController( } /** - * Apply all changes required when task is first added to desktop. Uses the task's current - * display by default to apply initial bounds and placement relative to the display. Use a - * different [displayId] if the task should be moved to a different display. + * Applies the [wct] changes needed when a task is first moving to a desk. + * + * Note that this recalculates the initial bounds of the task, so it should not be used when + * transferring a task between desks. + * + * TODO: b/362720497 - this should be improved to be reusable by desk-to-desk CUJs where + * [DesksOrganizer.moveTaskToDesk] needs to be called and even cross-display CUJs where + * [applyFreeformDisplayChange] needs to be called. Potentially by comparing source vs + * destination desk ids and display ids, or adding extra arguments to the function. */ - @VisibleForTesting - @Deprecated("Deprecated with multiple desks", ReplaceWith("prepareMoveTaskToDesk()")) - fun addMoveToDesktopChanges( + fun addMoveToDeskTaskChanges( wct: WindowContainerTransaction, - taskInfo: RunningTaskInfo, - displayId: Int = taskInfo.displayId, - ) { - val displayLayout = displayController.getDisplayLayout(displayId) ?: return - val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)!! - val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode - // TODO: b/397437641 - reconsider the windowing mode choice when multiple desks is enabled. - val targetWindowingMode = - if (tdaWindowingMode == WINDOWING_MODE_FREEFORM) { - // Display windowing is freeform, set to undefined and inherit it - WINDOWING_MODE_UNDEFINED - } else { - WINDOWING_MODE_FREEFORM - } - val initialBounds = getInitialBounds(displayLayout, taskInfo, displayId) - - if (canChangeTaskPosition(taskInfo)) { - wct.setBounds(taskInfo.token, initialBounds) - } - wct.setWindowingMode(taskInfo.token, targetWindowingMode) - wct.reorder(taskInfo.token, /* onTop= */ true) - if (useDesktopOverrideDensity()) { - wct.setDensityDpi(taskInfo.token, DESKTOP_DENSITY_OVERRIDE) - } - } - - private fun prepareMoveTaskToDesk( - wct: WindowContainerTransaction, - taskInfo: RunningTaskInfo, + task: RunningTaskInfo, deskId: Int, ) { - if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) return - val displayId = taskRepository.getDisplayForDesk(deskId) - val displayLayout = displayController.getDisplayLayout(displayId) ?: return - val initialBounds = getInitialBounds(displayLayout, taskInfo, displayId) - if (canChangeTaskPosition(taskInfo)) { - wct.setBounds(taskInfo.token, initialBounds) + val targetDisplayId = taskRepository.getDisplayForDesk(deskId) + val displayLayout = displayController.getDisplayLayout(targetDisplayId) ?: return + val initialBounds = getInitialBounds(displayLayout, task, targetDisplayId) + if (canChangeTaskPosition(task)) { + wct.setBounds(task.token, initialBounds) + } + if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) { + desksOrganizer.moveTaskToDesk(wct = wct, deskId = deskId, task = task) + } else { + val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(targetDisplayId)!! + val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode + val targetWindowingMode = + if (tdaWindowingMode == WINDOWING_MODE_FREEFORM) { + // Display windowing is freeform, set to undefined and inherit it + WINDOWING_MODE_UNDEFINED + } else { + WINDOWING_MODE_FREEFORM + } + wct.setWindowingMode(task.token, targetWindowingMode) + wct.reorder(task.token, /* onTop= */ true) } - desksOrganizer.moveTaskToDesk(wct, deskId = deskId, task = taskInfo) if (useDesktopOverrideDensity()) { - wct.setDensityDpi(taskInfo.token, DESKTOP_DENSITY_OVERRIDE) + wct.setDensityDpi(task.token, DESKTOP_DENSITY_OVERRIDE) } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java index d666126b91ba..c0a0f469add4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java @@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.service.dreams.Flags.dismissDreamOnKeyguardDismiss; import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS; +import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; @@ -200,7 +201,8 @@ public class KeyguardTransitionHandler transition, info, startTransaction, finishTransaction, finishCallback); } - if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0) { + if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0 + || (info.getFlags() & TRANSIT_FLAG_AOD_APPEARING) != 0) { return startAnimation(mAppearTransition, "appearing", transition, info, startTransaction, finishTransaction, finishCallback); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java index d16c5782177e..6012fe66188d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java @@ -32,6 +32,7 @@ import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; import android.view.SurfaceControl; +import android.window.DesktopExperienceFlags; import android.window.DisplayAreaInfo; import android.window.WindowContainerTransaction; @@ -41,7 +42,6 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLog; import com.android.internal.util.Preconditions; -import com.android.window.flags.Flags; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayChangeController; @@ -303,7 +303,8 @@ public class PipController implements ConfigurationChangeListener, public void onDisplayRemoved(int displayId) { // If PiP was active on an external display that is removed, clean up states and set // {@link PipDisplayLayoutState} to DEFAULT_DISPLAY. - if (Flags.enableConnectedDisplaysPip() && mPipTransitionState.isInPip() + if (DesktopExperienceFlags.ENABLE_CONNECTED_DISPLAYS_PIP.isTrue() + && mPipTransitionState.isInPip() && displayId == mPipDisplayLayoutState.getDisplayId() && displayId != DEFAULT_DISPLAY) { mPipTransitionState.setState(PipTransitionState.EXITING_PIP); @@ -385,7 +386,7 @@ public class PipController implements ConfigurationChangeListener, // If PiP is enabled on Connected Displays, update PipDisplayLayoutState to have the correct // display info that PiP is entering in. - if (Flags.enableConnectedDisplaysPip()) { + if (DesktopExperienceFlags.ENABLE_CONNECTED_DISPLAYS_PIP.isTrue()) { final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(displayId); if (displayLayout != null) { mPipDisplayLayoutState.setDisplayId(displayId); 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 b0785df3542e..63bf6841dba4 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 @@ -1114,44 +1114,44 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun addMoveToDesktopChanges_gravityLeft_noBoundsApplied() { + fun addMoveToDeskTaskChanges_gravityLeft_noBoundsApplied() { setUpLandscapeDisplay() val task = setUpFullscreenTask(gravity = Gravity.LEFT) val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) assertThat(finalBounds).isEqualTo(Rect()) } @Test - fun addMoveToDesktopChanges_gravityRight_noBoundsApplied() { + fun addMoveToDeskTaskChanges_gravityRight_noBoundsApplied() { setUpLandscapeDisplay() val task = setUpFullscreenTask(gravity = Gravity.RIGHT) val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) assertThat(finalBounds).isEqualTo(Rect()) } @Test - fun addMoveToDesktopChanges_gravityTop_noBoundsApplied() { + fun addMoveToDeskTaskChanges_gravityTop_noBoundsApplied() { setUpLandscapeDisplay() val task = setUpFullscreenTask(gravity = Gravity.TOP) val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) assertThat(finalBounds).isEqualTo(Rect()) } @Test - fun addMoveToDesktopChanges_gravityBottom_noBoundsApplied() { + fun addMoveToDeskTaskChanges_gravityBottom_noBoundsApplied() { setUpLandscapeDisplay() val task = setUpFullscreenTask(gravity = Gravity.BOTTOM) val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) assertThat(finalBounds).isEqualTo(Rect()) @@ -1192,7 +1192,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) - fun addMoveToDesktopChanges_positionBottomRight() { + fun addMoveToDeskTaskChanges_positionBottomRight() { setUpLandscapeDisplay() val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) @@ -1201,7 +1201,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() val task = setUpFullscreenTask() val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) @@ -1210,7 +1210,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) - fun addMoveToDesktopChanges_positionTopLeft() { + fun addMoveToDeskTaskChanges_positionTopLeft() { setUpLandscapeDisplay() val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) @@ -1219,7 +1219,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() val task = setUpFullscreenTask() val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) @@ -1228,7 +1228,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) - fun addMoveToDesktopChanges_positionBottomLeft() { + fun addMoveToDeskTaskChanges_positionBottomLeft() { setUpLandscapeDisplay() val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) @@ -1237,7 +1237,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() val task = setUpFullscreenTask() val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) @@ -1246,7 +1246,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) - fun addMoveToDesktopChanges_positionTopRight() { + fun addMoveToDeskTaskChanges_positionTopRight() { setUpLandscapeDisplay() val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) @@ -1255,7 +1255,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() val task = setUpFullscreenTask() val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) @@ -1264,7 +1264,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) - fun addMoveToDesktopChanges_positionResetsToCenter() { + fun addMoveToDeskTaskChanges_positionResetsToCenter() { setUpLandscapeDisplay() val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) @@ -1273,7 +1273,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() val task = setUpFullscreenTask() val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) @@ -1282,7 +1282,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) - fun addMoveToDesktopChanges_lastWindowSnapLeft_positionResetsToCenter() { + fun addMoveToDeskTaskChanges_lastWindowSnapLeft_positionResetsToCenter() { setUpLandscapeDisplay() val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) @@ -1294,7 +1294,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() val task = setUpFullscreenTask() val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) @@ -1303,7 +1303,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) - fun addMoveToDesktopChanges_lastWindowSnapRight_positionResetsToCenter() { + fun addMoveToDeskTaskChanges_lastWindowSnapRight_positionResetsToCenter() { setUpLandscapeDisplay() val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) @@ -1321,7 +1321,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() val task = setUpFullscreenTask() val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) @@ -1330,7 +1330,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) - fun addMoveToDesktopChanges_lastWindowMaximised_positionResetsToCenter() { + fun addMoveToDeskTaskChanges_lastWindowMaximised_positionResetsToCenter() { setUpLandscapeDisplay() val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) @@ -1340,7 +1340,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() val task = setUpFullscreenTask() val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) @@ -1349,7 +1349,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS) - fun addMoveToDesktopChanges_defaultToCenterIfFree() { + fun addMoveToDeskTaskChanges_defaultToCenterIfFree() { setUpLandscapeDisplay() val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) @@ -1367,7 +1367,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() val task = setUpFullscreenTask() val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!)) @@ -1375,7 +1375,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun addMoveToDesktopChanges_excludeCaptionFromAppBounds_nonResizableLandscape() { + fun addMoveToDeskTaskChanges_excludeCaptionFromAppBounds_nonResizableLandscape() { setUpLandscapeDisplay() val task = setUpFullscreenTask( @@ -1385,7 +1385,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() whenever(desktopModeCompatPolicy.shouldExcludeCaptionFromAppBounds(task)).thenReturn(true) val initialAspectRatio = calculateAspectRatio(task) val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) val captionInsets = getAppHeaderHeight(context) @@ -1397,7 +1397,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - fun addMoveToDesktopChanges_excludeCaptionFromAppBounds_nonResizablePortrait() { + fun addMoveToDeskTaskChanges_excludeCaptionFromAppBounds_nonResizablePortrait() { setUpLandscapeDisplay() val task = setUpFullscreenTask( @@ -1407,7 +1407,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() whenever(desktopModeCompatPolicy.shouldExcludeCaptionFromAppBounds(task)).thenReturn(true) val initialAspectRatio = calculateAspectRatio(task) val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) val finalBounds = findBoundsChange(wct, task) val captionInsets = getAppHeaderHeight(context) @@ -1445,29 +1445,29 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun addMoveToDesktopChanges_landscapeDevice_userFullscreenOverride_defaultPortraitBounds() { + fun addMoveToDeskTaskChanges_landscapeDevice_userFullscreenOverride_defaultPortraitBounds() { setUpLandscapeDisplay() val task = setUpFullscreenTask(enableUserFullscreenOverride = true) val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) } @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun addMoveToDesktopChanges_landscapeDevice_systemFullscreenOverride_defaultPortraitBounds() { + fun addMoveToDeskTaskChanges_landscapeDevice_systemFullscreenOverride_defaultPortraitBounds() { setUpLandscapeDisplay() val task = setUpFullscreenTask(enableSystemFullscreenOverride = true) val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) } @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun addMoveToDesktopChanges_landscapeDevice_portraitResizableApp_aspectRatioOverridden() { + fun addMoveToDeskTaskChanges_landscapeDevice_portraitResizableApp_aspectRatioOverridden() { setUpLandscapeDisplay() val task = setUpFullscreenTask( @@ -1476,36 +1476,36 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() aspectRatioOverrideApplied = true, ) val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS) } @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun addMoveToDesktopChanges_portraitDevice_userFullscreenOverride_defaultPortraitBounds() { + fun addMoveToDeskTaskChanges_portraitDevice_userFullscreenOverride_defaultPortraitBounds() { setUpPortraitDisplay() val task = setUpFullscreenTask(enableUserFullscreenOverride = true) val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) } @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun addMoveToDesktopChanges_portraitDevice_systemFullscreenOverride_defaultPortraitBounds() { + fun addMoveToDeskTaskChanges_portraitDevice_systemFullscreenOverride_defaultPortraitBounds() { setUpPortraitDisplay() val task = setUpFullscreenTask(enableSystemFullscreenOverride = true) val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) } @Test @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - fun addMoveToDesktopChanges_portraitDevice_landscapeResizableApp_aspectRatioOverridden() { + fun addMoveToDeskTaskChanges_portraitDevice_landscapeResizableApp_aspectRatioOverridden() { setUpPortraitDisplay() val task = setUpFullscreenTask( @@ -1515,7 +1515,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() aspectRatioOverrideApplied = true, ) val wct = WindowContainerTransaction() - controller.addMoveToDesktopChanges(wct, task) + controller.addMoveToDeskTaskChanges(wct, task, deskId = 0) assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS) } @@ -2824,7 +2824,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test - @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun moveToNextDisplay_toDesktopInOtherDisplay_bringsExistingTasksToFront() { val transition = Binder() val sourceDeskId = 0 @@ -2856,7 +2855,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY, Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER, ) - @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) fun moveToNextDisplay_toDesktopInOtherDisplay_movesHomeAndWallpaperToFront() { val homeTask = setUpHomeTask(displayId = SECOND_DISPLAY) whenever(desktopWallpaperActivityTokenProvider.getToken(SECOND_DISPLAY)) @@ -3506,6 +3504,39 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTask_switchToDesktop_movesTaskToDesk() { + taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = 5) + setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 5) + taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 5) + + val fullscreenTask = createFullscreenTask() + val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask)) + + assertNotNull(wct, "should handle request") + verify(desksOrganizer).moveTaskToDesk(wct = wct, deskId = 5, task = fullscreenTask) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_fullscreenTaskThatWasInactiveInDesk_tracksDeskDeactivation() { + // Set up and existing desktop task in an active desk. + val inactiveInDeskTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0) + taskRepository.setDeskInactive(deskId = 0) + + // Now the task is launching as fullscreen. + inactiveInDeskTask.configuration.windowConfiguration.windowingMode = + WINDOWING_MODE_FULLSCREEN + val transition = Binder() + val wct = controller.handleRequest(transition, createTransition(inactiveInDeskTask)) + + // Desk is deactivated. + assertNotNull(wct, "should handle request") + verify(desksTransitionsObserver) + .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId = 0)) + } + + @Test fun handleRequest_fullscreenTask_freeformVisible_returnSwitchToFreeformWCT() { val homeTask = setUpHomeTask() val freeformTask = setUpFreeformTask() @@ -3681,6 +3712,20 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_freeformTaskFromInactiveDesk_tracksDeskDeactivation() { + val deskId = 0 + val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = deskId) + taskRepository.setDeskInactive(deskId = deskId) + + val transition = Binder() + controller.handleRequest(transition, createTransition(freeformTask)) + + verify(desksTransitionsObserver) + .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId)) + } + + @Test fun handleRequest_freeformTask_relaunchActiveTask_taskBecomesUndefined() { val freeformTask = setUpFreeformTask() markTaskHidden(freeformTask) @@ -3928,6 +3973,24 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND) + fun handleRequest_recentsAnimationRunning_relaunchActiveTask_tracksDeskDeactivation() { + // Set up a visible freeform task + val freeformTask = setUpFreeformTask(displayId = DEFAULT_DISPLAY, deskId = 0) + markTaskVisible(freeformTask) + + // Mark recents animation running + recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_ANIMATING) + + val transition = Binder() + controller.handleRequest(transition, createTransition(freeformTask)) + + desksTransitionsObserver.addPendingTransition( + DeskTransition.DeactivateDesk(transition, deskId = 0) + ) + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) fun handleRequest_topActivityTransparentWithoutDisplay_returnSwitchToFreeformWCT() { val freeformTask = setUpFreeformTask() @@ -4045,6 +4108,31 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase() } @Test + @EnableFlags( + Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, + Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY, + ) + fun handleRequest_systemUIActivityWithDisplayInFreeformTask_inDesktop_tracksDeskDeactivation() { + val deskId = 5 + taskRepository.addDesk(displayId = DEFAULT_DISPLAY, deskId = deskId) + taskRepository.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = deskId) + val systemUIPackageName = + context.resources.getString(com.android.internal.R.string.config_systemUi) + val baseComponent = ComponentName(systemUIPackageName, /* cls= */ "") + val task = + setUpFreeformTask(displayId = DEFAULT_DISPLAY).apply { + baseActivity = baseComponent + isTopActivityNoDisplay = false + } + + val transition = Binder() + controller.handleRequest(transition, createTransition(task)) + + verify(desksTransitionsObserver) + .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId)) + } + + @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY) fun handleRequest_systemUIActivityWithoutDisplay_returnSwitchToFreeformWCT() { val freeformTask = setUpFreeformTask() diff --git a/libs/hwui/CanvasTransform.cpp b/libs/hwui/CanvasTransform.cpp index 30e7a628f1f6..6f60d01e4395 100644 --- a/libs/hwui/CanvasTransform.cpp +++ b/libs/hwui/CanvasTransform.cpp @@ -55,12 +55,20 @@ SkColor makeDark(SkColor color) { } } +SkColor invert(SkColor color) { + Lab lab = sRGBToLab(color); + lab.L = 100 - lab.L; + return LabToSRGB(lab, SkColorGetA(color)); +} + SkColor transformColor(ColorTransform transform, SkColor color) { switch (transform) { case ColorTransform::Light: return makeLight(color); case ColorTransform::Dark: return makeDark(color); + case ColorTransform::Invert: + return invert(color); default: return color; } @@ -80,19 +88,6 @@ SkColor transformColorInverse(ColorTransform transform, SkColor color) { static void applyColorTransform(ColorTransform transform, SkPaint& paint) { if (transform == ColorTransform::None) return; - if (transform == ColorTransform::Invert) { - auto filter = SkHighContrastFilter::Make( - {/* grayscale= */ false, SkHighContrastConfig::InvertStyle::kInvertLightness, - /* contrast= */ 0.0f}); - - if (paint.getColorFilter()) { - paint.setColorFilter(SkColorFilters::Compose(filter, paint.refColorFilter())); - } else { - paint.setColorFilter(filter); - } - return; - } - SkColor newColor = transformColor(transform, paint.getColor()); paint.setColor(newColor); @@ -112,6 +107,22 @@ static void applyColorTransform(ColorTransform transform, SkPaint& paint) { paint.setShader(SkGradientShader::MakeLinear( info.fPoints, info.fColors, info.fColorOffsets, info.fColorCount, info.fTileMode, info.fGradientFlags, nullptr)); + } else { + if (transform == ColorTransform::Invert) { + // Since we're trying to invert every thing around this draw call, we invert + // the color of the draw call if we don't know what it is. + auto filter = SkHighContrastFilter::Make( + {/* grayscale= */ false, + SkHighContrastConfig::InvertStyle::kInvertLightness, + /* contrast= */ 0.0f}); + + if (paint.getColorFilter()) { + paint.setColorFilter(SkColorFilters::Compose(filter, paint.refColorFilter())); + } else { + paint.setColorFilter(filter); + } + return; + } } } @@ -150,8 +161,13 @@ bool transformPaint(ColorTransform transform, SkPaint* paint) { } bool transformPaint(ColorTransform transform, SkPaint* paint, BitmapPalette palette) { - palette = filterPalette(paint, palette); bool shouldInvert = false; + if (transform == ColorTransform::Invert && palette != BitmapPalette::Colorful) { + // When the transform is Invert we invert any image that is not deemed "colorful", + // regardless of calculated image brightness. + shouldInvert = true; + } + palette = filterPalette(paint, palette); if (palette == BitmapPalette::Light && transform == ColorTransform::Dark) { shouldInvert = true; } diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index 4801bd1038a3..8b4e59aa73e2 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -27,6 +27,7 @@ #include "DamageAccumulator.h" #include "Debug.h" +#include "FeatureFlags.h" #include "Properties.h" #include "TreeInfo.h" #include "VectorDrawable.h" @@ -398,26 +399,32 @@ void RenderNode::syncDisplayList(TreeObserver& observer, TreeInfo* info) { deleteDisplayList(observer, info); mDisplayList = std::move(mStagingDisplayList); if (mDisplayList) { - WebViewSyncData syncData{.applyForceDark = shouldEnableForceDark(info)}; + WebViewSyncData syncData{.applyForceDark = shouldEnableForceDark(info) || + (info && isForceInvertDark(*info))}; mDisplayList.syncContents(syncData); handleForceDark(info); } } +// Return true if the tree should use the force invert feature that inverts +// the entire tree to darken it. inline bool RenderNode::isForceInvertDark(TreeInfo& info) { - return CC_UNLIKELY( - info.forceDarkType == android::uirenderer::ForceDarkType::FORCE_INVERT_COLOR_DARK); + return CC_UNLIKELY(info.forceDarkType == + android::uirenderer::ForceDarkType::FORCE_INVERT_COLOR_DARK); } +// Return true if the tree should use the force dark feature that selectively +// darkens light nodes on the tree. inline bool RenderNode::shouldEnableForceDark(TreeInfo* info) { - return CC_UNLIKELY( - info && - (!info->disableForceDark || isForceInvertDark(*info))); + return CC_UNLIKELY(info && !info->disableForceDark); } - - -void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) { +void RenderNode::handleForceDark(TreeInfo *info) { + if (CC_UNLIKELY(view_accessibility_flags::force_invert_color() && info && + isForceInvertDark(*info))) { + mDisplayList.applyColorTransform(ColorTransform::Invert); + return; + } if (!shouldEnableForceDark(info)) { return; } @@ -427,13 +434,7 @@ void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) { children.push_back(node); }); if (mDisplayList.hasText()) { - if (isForceInvertDark(*info) && mDisplayList.hasFill()) { - // Handle a special case for custom views that draw both text and background in the - // same RenderNode, which would otherwise be altered to white-on-white text. - usage = UsageHint::Container; - } else { - usage = UsageHint::Foreground; - } + usage = UsageHint::Foreground; } if (usage == UsageHint::Unknown) { if (children.size() > 1) { diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp index 63a024b8e780..3ef970830dc4 100644 --- a/libs/hwui/hwui/Bitmap.cpp +++ b/libs/hwui/hwui/Bitmap.cpp @@ -16,6 +16,8 @@ #include "Bitmap.h" #include <android-base/file.h> + +#include "FeatureFlags.h" #include "HardwareBitmapUploader.h" #include "Properties.h" #ifdef __ANDROID__ // Layoutlib does not support render thread @@ -547,9 +549,16 @@ BitmapPalette Bitmap::computePalette(const SkImageInfo& info, const void* addr, } ALOGV("samples = %d, hue [min = %f, max = %f, avg = %f]; saturation [min = %f, max = %f, avg = " - "%f]", + "%f] %d x %d", sampledCount, hue.min(), hue.max(), hue.average(), saturation.min(), saturation.max(), - saturation.average()); + saturation.average(), info.width(), info.height()); + + if (CC_UNLIKELY(view_accessibility_flags::force_invert_color())) { + if (saturation.delta() > 0.1f || + (hue.delta() > 20 && saturation.average() > 0.2f && value.average() < 0.9f)) { + return BitmapPalette::Colorful; + } + } if (hue.delta() <= 20 && saturation.delta() <= .1f) { if (value.average() >= .5f) { diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h index 4e9bcf27c0ef..0fe5fe88f715 100644 --- a/libs/hwui/hwui/Bitmap.h +++ b/libs/hwui/hwui/Bitmap.h @@ -49,6 +49,7 @@ enum class BitmapPalette { Unknown, Light, Dark, + Colorful, }; namespace uirenderer { diff --git a/libs/hwui/jni/GIFMovie.cpp b/libs/hwui/jni/GIFMovie.cpp index 6c82aa1ca27d..476b6fda5007 100644 --- a/libs/hwui/jni/GIFMovie.cpp +++ b/libs/hwui/jni/GIFMovie.cpp @@ -63,7 +63,7 @@ GIFMovie::GIFMovie(SkStream* stream) } fCurrIndex = -1; fLastDrawIndex = -1; - fPaintingColor = SkPackARGB32(0, 0, 0, 0); + fPaintingColor = SK_AlphaTRANSPARENT; } GIFMovie::~GIFMovie() @@ -127,7 +127,7 @@ static void copyLine(uint32_t* dst, const unsigned char* src, const ColorMapObje for (; width > 0; width--, src++, dst++) { if (*src != transparent && *src < cmap->ColorCount) { const GifColorType& col = cmap->Colors[*src]; - *dst = SkPackARGB32(0xFF, col.Red, col.Green, col.Blue); + *dst = SkColorSetRGB(col.Red, col.Green, col.Blue); } } } @@ -395,10 +395,10 @@ bool GIFMovie::onGetBitmap(SkBitmap* bm) lastIndex = fGIF->ImageCount - 1; } - SkColor bgColor = SkPackARGB32(0, 0, 0, 0); + SkColor bgColor = SK_ColorTRANSPARENT; if (gif->SColorMap != nullptr && gif->SBackGroundColor < gif->SColorMap->ColorCount) { const GifColorType& col = gif->SColorMap->Colors[gif->SBackGroundColor]; - bgColor = SkColorSetARGB(0xFF, col.Red, col.Green, col.Blue); + bgColor = SkColorSetRGB(col.Red, col.Green, col.Blue); } // draw each frames - not intelligent way @@ -411,7 +411,7 @@ bool GIFMovie::onGetBitmap(SkBitmap* bm) if (!trans && gif->SColorMap != nullptr) { fPaintingColor = bgColor; } else { - fPaintingColor = SkColorSetARGB(0, 0, 0, 0); + fPaintingColor = SK_ColorTRANSPARENT; } bm->eraseColor(fPaintingColor); diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java index 80b55e2c1244..5a993bfcc9cf 100644 --- a/location/java/android/location/LocationRequest.java +++ b/location/java/android/location/LocationRequest.java @@ -33,6 +33,7 @@ import android.annotation.SystemApi; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; import android.content.pm.PackageManager; +import android.location.flags.Flags; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; @@ -181,7 +182,8 @@ public final class LocationRequest implements Parcelable { public static final int POWER_HIGH = 203; private static final long IMPLICIT_MIN_UPDATE_INTERVAL = -1; - private static final double IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR = 1D / 6D; + private static final double LEGACY_IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR = 1D / 6D; + private static final double IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR = 1D / 2D; private @Nullable String mProvider; private @Quality int mQuality; @@ -553,7 +555,10 @@ public final class LocationRequest implements Parcelable { */ public @IntRange(from = 0) long getMinUpdateIntervalMillis() { if (mMinUpdateIntervalMillis == IMPLICIT_MIN_UPDATE_INTERVAL) { - return (long) (mIntervalMillis * IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR); + if (Flags.updateMinLocationRequestInterval()) { + return (long) (mIntervalMillis * IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR); + } + return (long) (mIntervalMillis * LEGACY_IMPLICIT_MIN_UPDATE_INTERVAL_FACTOR); } else { // the min is only necessary in case someone use a deprecated function to mess with the // interval or min update interval diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig index 1b38982f48c1..83b1778fd611 100644 --- a/location/java/android/location/flags/location.aconfig +++ b/location/java/android/location/flags/location.aconfig @@ -168,3 +168,14 @@ flag { description: "Flag for GNSS assistance interface" bug: "209078566" } + +flag { + name: "update_min_location_request_interval" + namespace: "location" + description: "Flag for updating the default logic for the minimal interval for location request" + bug: "397444378" + metadata { + purpose: PURPOSE_BUGFIX + } +} + diff --git a/packages/EasterEgg/src/com/android/egg/landroid/Autopilot.kt b/packages/EasterEgg/src/com/android/egg/landroid/Autopilot.kt index 8214c540304e..a4803d248ea8 100644 --- a/packages/EasterEgg/src/com/android/egg/landroid/Autopilot.kt +++ b/packages/EasterEgg/src/com/android/egg/landroid/Autopilot.kt @@ -44,7 +44,7 @@ class Autopilot(val ship: Spacecraft, val universe: Universe) : Entity { if (enabled) listOf( "---- AUTOPILOT ENGAGED ----", - "TGT: " + (target?.name?.toUpperCase() ?: "SELECTING..."), + "TGT: " + (target?.name?.uppercase() ?: "SELECTING..."), "EXE: $strategy" + if (debug.isNotEmpty()) " ($debug)" else "", ) .joinToString("\n") diff --git a/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt b/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt index 95a60c7a5292..61ea08ecbf5d 100644 --- a/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt +++ b/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt @@ -236,8 +236,8 @@ fun Telemetry(universe: Universe, showControls: Boolean) { ((closest.pos - pos).mag() - closest.radius).toInt() listOfNotNull( landing?.let { - "LND: ${it.planet.name.toUpperCase()}\n" + - "JOB: ${it.text.toUpperCase()}" + "LND: ${it.planet.name.uppercase()}\n" + + "JOB: ${it.text.uppercase()}" } ?: if (distToClosest < 10_000) { "ALT: $distToClosest" diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index f692601edcb7..3c70fc15485a 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -96,6 +96,7 @@ public class SecureSettings { Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY, Settings.Secure.ACCESSIBILITY_AUTOCLICK_PANEL_POSITION, + Settings.Secure.ACCESSIBILITY_AUTOCLICK_REVERT_TO_LEFT_CLICK, Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON, Settings.Secure.PREFERRED_TTY_MODE, Settings.Secure.ENHANCED_VOICE_PRIVACY_ENABLED, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index e42d3fb9da41..c09e45ed81a6 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -147,6 +147,7 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_DELAY, NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_PANEL_POSITION, ANY_STRING_VALIDATOR); + VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_REVERT_TO_LEFT_CLICK, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ACCESSIBILITY_LARGE_POINTER_ICON, BOOLEAN_VALIDATOR); VALIDATORS.put( Secure.PREFERRED_TTY_MODE, diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index c29a5a20a96f..167674a451b3 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -1722,6 +1722,9 @@ class SettingsProtoDumpUtil { Settings.Secure.ACCESSIBILITY_AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT, SecureSettingsProto.Accessibility.AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT); dumpSetting(s, p, + Settings.Secure.ACCESSIBILITY_AUTOCLICK_REVERT_TO_LEFT_CLICK, + SecureSettingsProto.Accessibility.AUTOCLICK_REVERT_TO_LEFT_CLICK); + dumpSetting(s, p, Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, SecureSettingsProto.Accessibility.AUTOCLICK_ENABLED); dumpSetting(s, p, diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt index b9200c15944c..e9e61a718f08 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt @@ -44,6 +44,7 @@ class ComposedDigitalLayerController(private val clockCtx: ClockContext) : val dozeState = DefaultClockController.AnimationState(1F) override val view = FlexClockView(clockCtx) + override var onViewBoundsChanged by view::onViewBoundsChanged init { fun createController(cfg: LayerConfig) { diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt index f24ad1c10c4a..9bb3bac824e9 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt @@ -17,6 +17,7 @@ import android.content.Context import android.content.res.Resources import android.graphics.Color import android.graphics.Rect +import android.graphics.RectF import android.icu.text.NumberFormat import android.util.TypedValue import android.view.LayoutInflater @@ -97,7 +98,12 @@ class DefaultClockController( events.onLocaleChanged(Locale.getDefault()) } - override fun initialize(isDarkTheme: Boolean, dozeFraction: Float, foldFraction: Float) { + override fun initialize( + isDarkTheme: Boolean, + dozeFraction: Float, + foldFraction: Float, + onBoundsChanged: (RectF) -> Unit, + ) { largeClock.recomputePadding(null) largeClock.animations = LargeClockAnimations(largeClock.view, dozeFraction, foldFraction) diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt index 1a1033ba42e0..6dfd2268005f 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt @@ -16,6 +16,7 @@ package com.android.systemui.shared.clocks +import android.graphics.RectF import com.android.systemui.animation.GSFAxes import com.android.systemui.customization.R import com.android.systemui.plugins.clocks.AlarmData @@ -102,9 +103,15 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController } } - override fun initialize(isDarkTheme: Boolean, dozeFraction: Float, foldFraction: Float) { + override fun initialize( + isDarkTheme: Boolean, + dozeFraction: Float, + foldFraction: Float, + onBoundsChanged: (RectF) -> Unit, + ) { events.onFontAxesChanged(clockCtx.settings.axes) smallClock.run { + layerController.onViewBoundsChanged = onBoundsChanged events.onThemeChanged(theme.copy(isDarkTheme = isDarkTheme)) animations.doze(dozeFraction) animations.fold(foldFraction) @@ -112,6 +119,7 @@ class FlexClockController(private val clockCtx: ClockContext) : ClockController } largeClock.run { + layerController.onViewBoundsChanged = onBoundsChanged events.onThemeChanged(theme.copy(isDarkTheme = isDarkTheme)) animations.doze(dozeFraction) animations.fold(foldFraction) diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt index af00cc264208..336c66eed889 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt @@ -16,6 +16,7 @@ package com.android.systemui.shared.clocks +import android.graphics.RectF import android.view.View import androidx.annotation.VisibleForTesting import com.android.systemui.plugins.clocks.ClockAnimations @@ -31,4 +32,5 @@ interface SimpleClockLayerController { val config: ClockFaceConfig @VisibleForTesting var fakeTimeMills: Long? + var onViewBoundsChanged: ((RectF) -> Unit)? } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt index 0b7ea1a335ef..97004ef6f9a9 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt @@ -85,6 +85,7 @@ open class SimpleDigitalHandLayerController( override val view = SimpleDigitalClockTextView(clockCtx, isLargeClock) private val logger = Logger(clockCtx.messageBuffer, TAG) val timespec = DigitalTimespecHandler(layerCfg.timespec, layerCfg.dateTimeFormat) + override var onViewBoundsChanged by view::onViewBoundsChanged @VisibleForTesting override var fakeTimeMills: Long? diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt index 2d0ca5331299..c765ea9cc84c 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt @@ -100,6 +100,7 @@ class FlexClockView(clockCtx: ClockContext) : ViewGroup(clockCtx.context) { updateLocale(Locale.getDefault()) } + var onViewBoundsChanged: ((RectF) -> Unit)? = null private val digitOffsets = mutableMapOf<Int, Float>() protected fun calculateSize( @@ -189,13 +190,21 @@ class FlexClockView(clockCtx: ClockContext) : ViewGroup(clockCtx.context) { fun updateLocation() { val layoutBounds = this.layoutBounds ?: return + val bounds = + RectF( + layoutBounds.centerX() - measuredWidth / 2f, + layoutBounds.centerY() - measuredHeight / 2f, + layoutBounds.centerX() + measuredWidth / 2f, + layoutBounds.centerY() + measuredHeight / 2f, + ) setFrame( - (layoutBounds.centerX() - measuredWidth / 2f).roundToInt(), - (layoutBounds.centerY() - measuredHeight / 2f).roundToInt(), - (layoutBounds.centerX() + measuredWidth / 2f).roundToInt(), - (layoutBounds.centerY() + measuredHeight / 2f).roundToInt(), + bounds.left.roundToInt(), + bounds.top.roundToInt(), + bounds.right.roundToInt(), + bounds.bottom.roundToInt(), ) updateChildFrames(isLayout = false) + onViewBoundsChanged?.let { it(bounds) } } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt index 015a82707ea2..5aea88de061c 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt @@ -117,6 +117,7 @@ open class SimpleDigitalClockTextView( fidgetFontVariation = buildFidgetVariation(lsFontAxes).toFVar() } + var onViewBoundsChanged: ((RectF) -> Unit)? = null private val parser = DimensionParser(clockCtx.context) var maxSingleDigitHeight = -1f var maxSingleDigitWidth = -1f @@ -497,6 +498,7 @@ open class SimpleDigitalClockTextView( targetRect.right.roundToInt(), targetRect.bottom.roundToInt(), ) + onViewBoundsChanged?.let { it(targetRect) } return targetRect } diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index 189d554415d0..f4d4b1efa3e2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -25,6 +25,8 @@ import android.view.Gravity import android.view.LayoutInflater import android.view.MotionEvent import android.view.View +import android.view.ViewTreeObserver +import android.view.ViewTreeObserver.OnPreDrawListener import android.view.WindowInsetsController import android.widget.FrameLayout import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -720,6 +722,37 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { } @Test + fun startAppearAnimation_ifDelayed() { + val argumentCaptor = ArgumentCaptor.forClass(OnPreDrawListener::class.java) + whenever(view.isAppearAnimationDelayed).thenReturn(true) + val viewTreeObserver: ViewTreeObserver = mock() + whenever(view.viewTreeObserver).thenReturn(viewTreeObserver) + + underTest.startAppearAnimationIfDelayed() + + verify(view).alpha = 1f + verify(viewTreeObserver).addOnPreDrawListener(argumentCaptor.capture()) + argumentCaptor.value.onPreDraw() + + verify(view).startAppearAnimation(any(SecurityMode::class.java)) + verify(view).setIsAppearAnimationDelayed(false) + } + + @Test + fun appearAnimation_willNotStart_ifNotDelayed() { + whenever(view.isAppearAnimationDelayed).thenReturn(false) + val viewTreeObserver: ViewTreeObserver = mock() + whenever(view.viewTreeObserver).thenReturn(viewTreeObserver) + + underTest.startAppearAnimationIfDelayed() + + verify(view, never()).alpha + verify(viewTreeObserver, never()).addOnPreDrawListener(any()) + + verify(view, never()).startAppearAnimation(any(SecurityMode::class.java)) + } + + @Test fun gravityReappliedOnConfigurationChange() { // Set initial gravity testableResources.addOverride(R.integer.keyguard_host_view_gravity, Gravity.CENTER) diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index 176824fd4c5d..2845f6a2983a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java @@ -452,6 +452,14 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { verify(keyguardPasswordView).setDisappearAnimationListener(any()); } + @Test + public void setupForDelayedAppear() { + mKeyguardSecurityContainer.setupForDelayedAppear(); + assertThat(mKeyguardSecurityContainer.getTranslationY()).isEqualTo(0f); + assertThat(mKeyguardSecurityContainer.getAlpha()).isEqualTo(0f); + assertThat(mKeyguardSecurityContainer.isAppearAnimationDelayed()).isTrue(); + } + private BackEvent createBackEvent(float touchX, float progress) { return new BackEvent(0, 0, progress, BackEvent.EDGE_LEFT); } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java index f53f964cd3d9..191ecccd5f71 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java @@ -28,6 +28,8 @@ import static org.mockito.Mockito.when; import android.animation.ValueAnimator; import android.content.pm.UserInfo; +import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Rect; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; @@ -54,6 +56,7 @@ import com.android.systemui.scene.ui.view.WindowRootView; import com.android.systemui.shared.system.InputChannelCompat; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.CentralSurfaces; +import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.wm.shell.animation.FlingAnimationUtils; import org.junit.Before; @@ -127,10 +130,16 @@ public class BouncerFullscreenSwipeTouchHandlerTest extends SysuiTestCase { @Mock WindowRootView mWindowRootView; + @Mock + Resources mResources; + private SceneInteractor mSceneInteractor; + private KeyguardStateController mKeyguardStateController; + private static final float TOUCH_REGION = .3f; private static final float MIN_BOUNCER_HEIGHT = .05f; + private final Configuration mConfiguration = new Configuration(); private static final Rect SCREEN_BOUNDS = new Rect(0, 0, 1024, 100); private static final UserInfo CURRENT_USER_INFO = new UserInfo( @@ -153,6 +162,8 @@ public class BouncerFullscreenSwipeTouchHandlerTest extends SysuiTestCase { public void setup() { mKosmos = new KosmosJavaAdapter(this); mSceneInteractor = spy(mKosmos.getSceneInteractor()); + mKeyguardStateController = mKosmos.getKeyguardStateController(); + mConfiguration.orientation = Configuration.ORIENTATION_PORTRAIT; MockitoAnnotations.initMocks(this); mTouchHandler = new BouncerSwipeTouchHandler( @@ -172,7 +183,9 @@ public class BouncerFullscreenSwipeTouchHandlerTest extends SysuiTestCase { mKeyguardInteractor, mSceneInteractor, mKosmos.getShadeRepository(), - Optional.of(() -> mWindowRootView)); + Optional.of(() -> mWindowRootView), + mKeyguardStateController, + mKosmos.getCommunalSettingsInteractor()); when(mScrimManager.getCurrentController()).thenReturn(mScrimController); when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator); @@ -180,6 +193,9 @@ public class BouncerFullscreenSwipeTouchHandlerTest extends SysuiTestCase { when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn(Float.MAX_VALUE); when(mTouchSession.getBounds()).thenReturn(SCREEN_BOUNDS); when(mKeyguardInteractor.isKeyguardDismissible()).thenReturn(MutableStateFlow(false)); + when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(true); + when(mWindowRootView.getResources()).thenReturn(mResources); + when(mResources.getConfiguration()).thenReturn(mConfiguration); } /** diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java index dd43d817cccc..e8dc6762cc92 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java @@ -16,6 +16,10 @@ package com.android.systemui.ambient.touch; +import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf; + +import static com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2; + import static com.google.common.truth.Truth.assertThat; import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow; @@ -34,6 +38,8 @@ import static org.mockito.Mockito.when; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.content.pm.UserInfo; +import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Rect; import android.graphics.Region; import android.platform.test.annotations.DisableFlags; @@ -63,6 +69,7 @@ import com.android.systemui.shade.ShadeExpansionChangeEvent; import com.android.systemui.shared.system.InputChannelCompat; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.CentralSurfaces; +import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.wm.shell.animation.FlingAnimationUtils; import org.junit.Before; @@ -132,12 +139,16 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { @Mock WindowRootView mWindowRootView; + Resources mResources; + @Mock CommunalViewModel mCommunalViewModel; @Mock KeyguardInteractor mKeyguardInteractor; + private KeyguardStateController mKeyguardStateController; + @Captor ArgumentCaptor<Rect> mRectCaptor; @@ -147,6 +158,7 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { private static final int SCREEN_WIDTH_PX = 1024; private static final int SCREEN_HEIGHT_PX = 100; private static final float MIN_BOUNCER_HEIGHT = .05f; + private final Configuration mConfiguration = new Configuration(); private static final Rect SCREEN_BOUNDS = new Rect(0, 0, 1024, 100); private static final UserInfo CURRENT_USER_INFO = new UserInfo( @@ -157,7 +169,8 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { @Parameters(name = "{0}") public static List<FlagsParameterization> getParams() { - return SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag(); + return SceneContainerFlagParameterizationKt + .andSceneContainer(allCombinationsOf(Flags.FLAG_GLANCEABLE_HUB_V2)); } public BouncerSwipeTouchHandlerTest(FlagsParameterization flags) { @@ -168,7 +181,13 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { @Before public void setup() { mKosmos = new KosmosJavaAdapter(this); + mContext.ensureTestableResources(); + mResources = mContext.getResources(); + overrideConfiguration(mConfiguration); + mConfiguration.orientation = Configuration.ORIENTATION_PORTRAIT; + mSceneInteractor = spy(mKosmos.getSceneInteractor()); + mKeyguardStateController = mKosmos.getKeyguardStateController(); MockitoAnnotations.initMocks(this); mTouchHandler = new BouncerSwipeTouchHandler( @@ -188,7 +207,9 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { mKeyguardInteractor, mSceneInteractor, mKosmos.getShadeRepository(), - Optional.of(() -> mWindowRootView) + Optional.of(() -> mWindowRootView), + mKeyguardStateController, + mKosmos.getCommunalSettingsInteractor() ); when(mScrimManager.getCurrentController()).thenReturn(mScrimController); @@ -197,6 +218,9 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn(Float.MAX_VALUE); when(mTouchSession.getBounds()).thenReturn(SCREEN_BOUNDS); when(mKeyguardInteractor.isKeyguardDismissible()).thenReturn(MutableStateFlow(false)); + when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(true); + when(mWindowRootView.getResources()).thenReturn(mResources); + setCommunalV2ConfigEnabled(true); } /** @@ -586,6 +610,43 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { verify(mUiEventLogger).log(BouncerSwipeTouchHandler.DreamEvent.DREAM_BOUNCER_FULLY_VISIBLE); } + @Test + @DisableFlags(Flags.FLAG_SCENE_CONTAINER) + @EnableFlags(FLAG_GLANCEABLE_HUB_V2) + public void swipeUpAboveThresholdInLandscape_keyguardRotationNotAllowed_showsBouncer() { + when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false); + mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE; + + final float swipeUpPercentage = .1f; + // The upward velocity is ignored. + final float velocityY = -1; + swipeToPosition(swipeUpPercentage, velocityY); + + // Ensure show bouncer scrimmed + verify(mScrimController).show(true); + verify(mValueAnimatorCreator, never()).create(anyFloat(), anyFloat()); + verify(mValueAnimator, never()).start(); + } + + @Test + @DisableFlags(Flags.FLAG_SCENE_CONTAINER) + @EnableFlags(FLAG_GLANCEABLE_HUB_V2) + public void swipeUpBelowThreshold_inLandscapeKeyguardRotationNotAllowed_noBouncer() { + mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE; + + final float swipeUpPercentage = .02f; + // The upward velocity is ignored. + final float velocityY = -1; + swipeToPosition(swipeUpPercentage, velocityY); + + // no bouncer shown scrimmed + verify(mScrimController, never()).show(true); + // on touch end, bouncer hidden + verify(mValueAnimatorCreator).create(eq(1 - swipeUpPercentage), + eq(KeyguardBouncerConstants.EXPANSION_HIDDEN)); + verify(mValueAnimator, never()).addListener(any()); + } + /** * Tests that swiping up with a speed above the set threshold will continue the expansion. */ @@ -672,4 +733,15 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { inputEventListenerCaptor.getValue().onInputEvent(upEvent); } + + private void setCommunalV2ConfigEnabled(boolean enabled) { + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.bool.config_glanceableHubEnabled, + enabled); + } + + private void overrideConfiguration(Configuration configuration) { + mContext.getOrCreateTestableResources().overrideConfiguration( + configuration); + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt index 4d027088ca1a..5249bbe2a861 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt @@ -26,7 +26,10 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.airbnb.lottie.LottieAnimationView import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.biometricStatusRepository import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository +import com.android.systemui.biometrics.shared.model.AuthenticationReason +import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations import com.android.systemui.biometrics.shared.model.DisplayRotation import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.SensorStrength @@ -50,7 +53,6 @@ import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito.any import org.mockito.Mockito.inOrder -import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.`when` @@ -67,6 +69,7 @@ class SideFpsOverlayViewBinderTest : SysuiTestCase() { @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule() @Mock private lateinit var layoutInflater: LayoutInflater @Mock private lateinit var sideFpsView: View + @Mock private lateinit var lottieAnimationView: LottieAnimationView @Captor private lateinit var viewCaptor: ArgumentCaptor<View> @Before @@ -76,7 +79,7 @@ class SideFpsOverlayViewBinderTest : SysuiTestCase() { context.addMockSystemService(WindowManager::class.java, kosmos.windowManager) `when`(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sideFpsView) `when`(sideFpsView.requireViewById<LottieAnimationView>(eq(R.id.sidefps_animation))) - .thenReturn(mock(LottieAnimationView::class.java)) + .thenReturn(lottieAnimationView) } @Test @@ -184,6 +187,20 @@ class SideFpsOverlayViewBinderTest : SysuiTestCase() { } } + @Test + fun verifyToggleAnimation_onSideFpsIndicatorViewClickedWhileEnrolling() { + kosmos.testScope.runTest { + kosmos.biometricStatusRepository.setFingerprintAuthenticationReason( + AuthenticationReason.SettingsAuthentication(SettingsOperations.ENROLL_ENROLLING) + ) + setupTestConfiguration(isInRearDisplayMode = false) + val clickListenerCaptor = ArgumentCaptor.forClass(View.OnClickListener::class.java) + verify(sideFpsView).setOnClickListener(clickListenerCaptor.capture()) + clickListenerCaptor.value.onClick(sideFpsView) + verify(lottieAnimationView).toggleAnimation() + } + } + private suspend fun TestScope.setupTestConfiguration(isInRearDisplayMode: Boolean) { kosmos.fingerprintPropertyRepository.setProperties( sensorId = 1, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt index dd4af7bb780e..53ddcfaf9576 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt @@ -20,6 +20,7 @@ import android.content.testableContext import android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState import com.android.internal.logging.uiEventLoggerFake import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository @@ -39,13 +40,22 @@ import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runCurrent +import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope import com.android.systemui.power.data.repository.fakePowerRepository import com.android.systemui.res.R +import com.android.systemui.scene.data.repository.sceneContainerRepository +import com.android.systemui.scene.shared.model.Overlays +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.scene.transitionState import com.android.systemui.testKosmos import com.android.systemui.util.settings.fakeGlobalSettings import com.google.common.truth.Truth.assertThat import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -368,6 +378,76 @@ class BouncerInteractorTest : SysuiTestCase() { testableResources.removeOverride(R.bool.can_use_one_handed_bouncer) } + @Test + fun bouncerExpansion_lockscreenToBouncer() = + kosmos.runTest { + val bouncerExpansion by collectLastValue(underTest.bouncerExpansion) + + val progress = MutableStateFlow(0f) + kosmos.sceneContainerRepository.setTransitionState(transitionState) + transitionState.value = + ObservableTransitionState.Transition.showOverlay( + overlay = Overlays.Bouncer, + fromScene = Scenes.Lockscreen, + currentOverlays = flowOf(emptySet()), + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + + assertThat(bouncerExpansion).isEqualTo(0f) + + progress.value = 1f + assertThat(bouncerExpansion).isEqualTo(1f) + } + + @Test + fun bouncerExpansion_BouncerToLockscreen() = + kosmos.runTest { + val bouncerExpansion by collectLastValue(underTest.bouncerExpansion) + + val progress = MutableStateFlow(0f) + kosmos.sceneContainerRepository.setTransitionState(transitionState) + transitionState.value = + ObservableTransitionState.Transition.hideOverlay( + overlay = Overlays.Bouncer, + toScene = Scenes.Lockscreen, + currentOverlays = flowOf(emptySet()), + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + + assertThat(bouncerExpansion).isEqualTo(1f) + + progress.value = 1f + assertThat(bouncerExpansion).isEqualTo(0f) + } + + @Test + fun bouncerExpansion_shadeToLockscreenUnderBouncer() = + kosmos.runTest { + val bouncerExpansion by collectLastValue(underTest.bouncerExpansion) + + val progress = MutableStateFlow(0f) + kosmos.sceneContainerRepository.setTransitionState(transitionState) + transitionState.value = + ObservableTransitionState.Transition( + fromScene = Scenes.Shade, + toScene = Scenes.Lockscreen, + currentScene = flowOf(Scenes.Lockscreen), + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + currentOverlays = setOf(Overlays.Bouncer), + ) + + assertThat(bouncerExpansion).isEqualTo(1f) + + progress.value = 1f + assertThat(bouncerExpansion).isEqualTo(1f) + } + companion object { private const val MESSAGE_ENTER_YOUR_PIN = "Enter your PIN" private const val MESSAGE_ENTER_YOUR_PASSWORD = "Enter your password" diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt index 7051f81cfc88..f58391496e65 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt @@ -22,6 +22,7 @@ import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import android.provider.Settings import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState import com.android.internal.logging.uiEventLoggerFake import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2 @@ -38,8 +39,13 @@ import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED import com.android.systemui.flags.andSceneContainer import com.android.systemui.flags.fakeFeatureFlagsClassic 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.keyguardInteractor +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.shared.model.TransitionStep import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.collectLastValue @@ -56,11 +62,15 @@ import com.google.common.truth.Truth.assertThat import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.advanceTimeBy import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito +import org.mockito.kotlin.verify import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters @@ -93,10 +103,12 @@ class CommunalSceneStartableTest(flags: FlagsParameterization) : SysuiTestCase() communalInteractor = communalInteractor, communalSettingsInteractor = communalSettingsInteractor, communalSceneInteractor = communalSceneInteractor, + keyguardTransitionInteractor = keyguardTransitionInteractor, keyguardInteractor = keyguardInteractor, systemSettings = fakeSettings, notificationShadeWindowController = notificationShadeWindowController, bgScope = applicationCoroutineScope, + applicationScope = applicationCoroutineScope, mainDispatcher = testDispatcher, uiEventLogger = uiEventLoggerFake, ) @@ -111,13 +123,13 @@ class CommunalSceneStartableTest(flags: FlagsParameterization) : SysuiTestCase() UserHandle.USER_CURRENT, ) fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true) + setCommunalV2ConfigEnabled(true) underTest.start() // Make communal available so that communalInteractor.desiredScene accurately reflects // scene changes instead of just returning Blank. runBlocking { setCommunalAvailable(true) } - setCommunalV2ConfigEnabled(true) } } @@ -414,6 +426,107 @@ class CommunalSceneStartableTest(flags: FlagsParameterization) : SysuiTestCase() assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) } + @Test + @DisableFlags(FLAG_SCENE_CONTAINER) + @EnableFlags(FLAG_GLANCEABLE_HUB_V2) + fun glanceableHubOrientationAware_idleOnCommunal() = + kosmos.runTest { + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") + + val scene by collectLastValue(communalSceneInteractor.currentScene) + assertThat(scene).isEqualTo(CommunalScenes.Communal) + + verify(notificationShadeWindowController).setGlanceableHubOrientationAware(true) + } + + @Test + @DisableFlags(FLAG_SCENE_CONTAINER) + @EnableFlags(FLAG_GLANCEABLE_HUB_V2) + fun glanceableHubOrientationAware_transitioningToCommunal() = + kosmos.runTest { + val progress = MutableStateFlow(0f) + val targetScene = CommunalScenes.Communal + val currentScene = CommunalScenes.Blank + val transitionState = + MutableStateFlow( + ObservableTransitionState.Transition( + fromScene = currentScene, + toScene = targetScene, + currentScene = flowOf(targetScene), + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + communalSceneInteractor.setTransitionState(transitionState) + + // Partially transition. + progress.value = .4f + + val scene by collectLastValue(communalSceneInteractor.currentScene) + assertThat(scene).isEqualTo(CommunalScenes.Blank) + + verify(notificationShadeWindowController).setGlanceableHubOrientationAware(true) + } + + @Test + @DisableFlags(FLAG_SCENE_CONTAINER) + @EnableFlags(FLAG_GLANCEABLE_HUB_V2) + fun glanceableHubOrientationAware_communalToDreaming() = + kosmos.runTest { + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") + + verify(notificationShadeWindowController).setGlanceableHubOrientationAware(true) + Mockito.clearInvocations(notificationShadeWindowController) + + val progress = MutableStateFlow(0f) + val currentScene = CommunalScenes.Communal + val targetScene = CommunalScenes.Blank + val transitionState = + MutableStateFlow( + ObservableTransitionState.Transition( + fromScene = currentScene, + toScene = targetScene, + currentScene = flowOf(targetScene), + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + communalSceneInteractor.setTransitionState(transitionState) + + // Partially transitioned out of Communal scene + progress.value = .4f + + // Started keyguard transitioning from hub -> dreaming. + fakeKeyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.DREAMING, + transitionState = TransitionState.STARTED, + ) + ) + verify(notificationShadeWindowController).setGlanceableHubOrientationAware(true) + Mockito.clearInvocations(notificationShadeWindowController) + + fakeKeyguardTransitionRepository.sendTransitionStep( + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.DREAMING, + transitionState = TransitionState.RUNNING, + value = 0.5f, + ) + + // Transitioned to dreaming. + fakeKeyguardTransitionRepository.sendTransitionStep( + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.DREAMING, + transitionState = TransitionState.FINISHED, + value = 1f, + ) + // Not on hub anymore, let other states take control + verify(notificationShadeWindowController).setGlanceableHubOrientationAware(false) + } + /** * Advances time by duration + 1 millisecond, to ensure that tasks scheduled to run at * currentTime + duration are scheduled. diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt index 77d7091e463a..dc21f0692c9e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt @@ -134,7 +134,7 @@ class CommunalSceneInteractorTest(flags: FlagsParameterization) : SysuiTestCase( underTest.snapToScene( CommunalScenes.Communal, "test", - ActivityTransitionAnimator.TIMINGS.totalDuration + ActivityTransitionAnimator.TIMINGS.totalDuration, ) assertThat(currentScene).isEqualTo(CommunalScenes.Blank) advanceTimeBy(ActivityTransitionAnimator.TIMINGS.totalDuration) @@ -269,6 +269,48 @@ class CommunalSceneInteractorTest(flags: FlagsParameterization) : SysuiTestCase( @DisableFlags(FLAG_SCENE_CONTAINER) @Test + fun isTransitioningToOrIdleOnCommunal() = + testScope.runTest { + // isIdleOnCommunal is false when not on communal. + val isTransitioningToOrIdleOnCommunal by + collectLastValue(underTest.isTransitioningToOrIdleOnCommunal) + assertThat(isTransitioningToOrIdleOnCommunal).isEqualTo(false) + + val transitionState: MutableStateFlow<ObservableTransitionState> = + MutableStateFlow( + ObservableTransitionState.Transition( + fromScene = CommunalScenes.Blank, + toScene = CommunalScenes.Communal, + currentScene = flowOf(CommunalScenes.Communal), + progress = flowOf(0f), + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + + // Start transition to communal. + repository.setTransitionState(transitionState) + assertThat(isTransitioningToOrIdleOnCommunal).isEqualTo(true) + + // Finish transition to communal + transitionState.value = ObservableTransitionState.Idle(CommunalScenes.Communal) + assertThat(isTransitioningToOrIdleOnCommunal).isEqualTo(true) + + // Start transition away from communal. + transitionState.value = + ObservableTransitionState.Transition( + fromScene = CommunalScenes.Communal, + toScene = CommunalScenes.Blank, + currentScene = flowOf(CommunalScenes.Blank), + progress = flowOf(.1f), + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + assertThat(isTransitioningToOrIdleOnCommunal).isEqualTo(false) + } + + @DisableFlags(FLAG_SCENE_CONTAINER) + @Test fun isCommunalVisible() = testScope.runTest { // isCommunalVisible is false when not on communal. diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelTest.kt index 6b9e23abd9a4..135e9a55e8b5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelTest.kt @@ -16,28 +16,46 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.content.res.mainResources import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND +import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2 import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.communal.domain.interactor.communalSceneInteractor +import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled +import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.flags.DisableSceneContainer import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.ui.transitions.blurConfig +import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.collectValues +import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.runTest +import com.android.systemui.statusbar.policy.keyguardStateController import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) class GlanceableHubToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() { - private val kosmos = testKosmos() + private val kosmos = + testKosmos().apply { mainResources = mContext.orCreateTestableResources.resources } private val underTest by lazy { kosmos.glanceableHubToPrimaryBouncerTransitionViewModel } + @Before + fun setUp() { + with(kosmos) { setCommunalV2ConfigEnabled(true) } + } + @Test @DisableSceneContainer @DisableFlags(FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND) @@ -84,4 +102,81 @@ class GlanceableHubToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() { }, ) } + + @Test + @DisableSceneContainer + @DisableFlags(FLAG_GLANCEABLE_HUB_V2) + fun willDelayBouncerAppearAnimation_flagDisabled_isFalse() = + kosmos.runTest { + // keyguard rotation is not allowed on device. + whenever(keyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false) + + val isIdleOnCommunal by collectLastValue(communalInteractor.isIdleOnCommunal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") + runCurrent() + // Device is idle on communal. + assertThat(isIdleOnCommunal).isTrue() + + // in landscape + assertThat(underTest.willDelayAppearAnimation(isLandscape = true)).isFalse() + // in portrait + assertThat(underTest.willDelayAppearAnimation(isLandscape = false)).isFalse() + } + + @Test + @DisableSceneContainer + @EnableFlags(FLAG_GLANCEABLE_HUB_V2) + fun willDelayBouncerAppearAnimation_keyguardRotationAllowed_isFalse() = + kosmos.runTest { + // Keyguard rotation is allowed on device. + whenever(keyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(true) + + val isIdleOnCommunal by collectLastValue(communalInteractor.isIdleOnCommunal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") + runCurrent() + // Device is idle on communal. + assertThat(isIdleOnCommunal).isTrue() + + // in landscape + assertThat(underTest.willDelayAppearAnimation(isLandscape = true)).isFalse() + // in portrait + assertThat(underTest.willDelayAppearAnimation(isLandscape = false)).isFalse() + } + + @Test + @DisableSceneContainer + @EnableFlags(FLAG_GLANCEABLE_HUB_V2) + fun willDelayBouncerAppearAnimation_isNotIdleOnCommunal_isFalse() = + kosmos.runTest { + whenever(keyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false) + + val isIdleOnCommunal by collectLastValue(communalInteractor.isIdleOnCommunal) + communalSceneInteractor.changeScene(CommunalScenes.Blank, "test") + runCurrent() + // Device is not on communal. + assertThat(isIdleOnCommunal).isFalse() + + // in landscape + assertThat(underTest.willDelayAppearAnimation(isLandscape = true)).isFalse() + // in portrait + assertThat(underTest.willDelayAppearAnimation(isLandscape = false)).isFalse() + } + + @Test + @DisableSceneContainer + @EnableFlags(FLAG_GLANCEABLE_HUB_V2) + fun willDelayBouncerAppearAnimation_isIdleOnCommunalAndKeyguardRotationIsNotAllowed() = + kosmos.runTest { + whenever(keyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false) + val isIdleOnCommunal by collectLastValue(communalInteractor.isIdleOnCommunal) + communalSceneInteractor.changeScene(CommunalScenes.Communal, "test") + runCurrent() + // Device is idle on communal. + assertThat(isIdleOnCommunal).isTrue() + + // Will delay in landscape + assertThat(underTest.willDelayAppearAnimation(isLandscape = true)).isTrue() + // Won't delay in portrait + assertThat(underTest.willDelayAppearAnimation(isLandscape = false)).isFalse() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index 764068ec1bf5..3407cd50e76f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -84,12 +84,12 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; -import java.util.List; -import java.util.concurrent.Executor; - import platform.test.runner.parameterized.ParameterizedAndroidJunit4; import platform.test.runner.parameterized.Parameters; +import java.util.List; +import java.util.concurrent.Executor; + @RunWith(ParameterizedAndroidJunit4.class) @RunWithLooper(setAsMainLooper = true) @SmallTest @@ -410,6 +410,19 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { } @Test + public void hubOrientationAware_layoutParamsUpdated() { + mNotificationShadeWindowController.setKeyguardShowing(false); + mNotificationShadeWindowController.setBouncerShowing(false); + mNotificationShadeWindowController.setGlanceableHubOrientationAware(true); + when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false); + mNotificationShadeWindowController.onConfigChanged(new Configuration()); + + verify(mWindowManager, atLeastOnce()).updateViewLayout(any(), mLayoutParameters.capture()); + assertThat(mLayoutParameters.getValue().screenOrientation) + .isEqualTo(ActivityInfo.SCREEN_ORIENTATION_USER); + } + + @Test public void batchApplyWindowLayoutParams_doesNotDispatchEvents() { mNotificationShadeWindowController.setForceDozeBrightness(true); verify(mWindowManager).updateViewLayout(any(), any()); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt index 9eba410ffdb5..4f301031e77c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt @@ -108,7 +108,7 @@ class DefaultClockProviderTest : SysuiTestCase() { verify(mockSmallClockView).setColors(DOZE_COLOR, Color.MAGENTA) verify(mockLargeClockView).setColors(DOZE_COLOR, Color.MAGENTA) - clock.initialize(true, 0f, 0f) + clock.initialize(true, 0f, 0f, {}) val expectedColor = 0 verify(mockSmallClockView).setColors(DOZE_COLOR, expectedColor) diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt index 32fec3277f18..4c1f6450dd78 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt @@ -13,6 +13,7 @@ */ package com.android.systemui.plugins.clocks +import android.graphics.RectF import com.android.systemui.plugins.annotations.ProtectedInterface import com.android.systemui.plugins.annotations.SimpleProperty import java.io.PrintWriter @@ -37,7 +38,12 @@ interface ClockController { val events: ClockEvents /** Initializes various rendering parameters. If never called, provides reasonable defaults. */ - fun initialize(isDarkTheme: Boolean, dozeFraction: Float, foldFraction: Float) + fun initialize( + isDarkTheme: Boolean, + dozeFraction: Float, + foldFraction: Float, + onBoundsChanged: (RectF) -> Unit, + ) /** Optional method for dumping debug information */ fun dump(pw: PrintWriter) diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index 6d446453d9f7..24fd86076d64 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -115,6 +115,7 @@ <dimen name="below_clock_padding_end">16dp</dimen> <dimen name="below_clock_padding_start_icons">28dp</dimen> <dimen name="smartspace_padding_horizontal">16dp</dimen> + <dimen name="smartspace_padding_vertical">12dp</dimen> <!-- Proportion of the screen height to use to set the maximum height of the bouncer to when the device is in the DEVICE_POSTURE_HALF_OPENED posture, for the PIN/pattern entry. 0 will diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 1b8282bf9b93..94342349e727 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -21,6 +21,7 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.res.Resources +import android.graphics.RectF import android.os.Trace import android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS import android.provider.Settings.Global.ZEN_MODE_OFF @@ -78,7 +79,7 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge @@ -147,7 +148,7 @@ constructor( val clockStr = clock.toString() loggers.forEach { it.d({ "New Clock: $str1" }) { str1 = clockStr } } - clock.initialize(isDarkTheme(), dozeAmount, 0f) + clock.initialize(isDarkTheme(), dozeAmount.value, 0f, { onClockBoundsChanged.value = it }) if (!regionSamplingEnabled) { updateColors() @@ -240,17 +241,16 @@ constructor( private var smallClockFrame: ViewGroup? = null private var onGlobalLayoutListener: OnGlobalLayoutListener? = null - private var isDozing = false - private set - private var isCharging = false - private var dozeAmount = 0f private var isKeyguardVisible = false private var isRegistered = false private var disposableHandle: DisposableHandle? = null private val regionSamplingEnabled = featureFlags.isEnabled(REGION_SAMPLING) private var largeClockOnSecondaryDisplay = false + val dozeAmount = MutableStateFlow(0f) + val onClockBoundsChanged = MutableStateFlow<RectF?>(null) + private fun isDarkTheme(): Boolean { val isLightTheme = TypedValue() context.theme.resolveAttribute(android.R.attr.isLightTheme, isLightTheme, true) @@ -306,7 +306,7 @@ constructor( var smallTimeListener: TimeListener? = null var largeTimeListener: TimeListener? = null val shouldTimeListenerRun: Boolean - get() = isKeyguardVisible && dozeAmount < DOZE_TICKRATE_THRESHOLD + get() = isKeyguardVisible && dozeAmount.value < DOZE_TICKRATE_THRESHOLD private var weatherData: WeatherData? = null private var zenData: ZenData? = null @@ -466,7 +466,6 @@ constructor( disposableHandle = parent.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { - listenForDozing(this) if (ModesUi.isEnabled) { listenForDnd(this) } @@ -576,17 +575,17 @@ constructor( } private fun handleDoze(doze: Float) { - dozeAmount = doze clock?.run { Trace.beginSection("$TAG#smallClock.animations.doze") - smallClock.animations.doze(dozeAmount) + smallClock.animations.doze(doze) Trace.endSection() Trace.beginSection("$TAG#largeClock.animations.doze") - largeClock.animations.doze(dozeAmount) + largeClock.animations.doze(doze) Trace.endSection() } smallTimeListener?.update(doze < DOZE_TICKRATE_THRESHOLD) largeTimeListener?.update(doze < DOZE_TICKRATE_THRESHOLD) + dozeAmount.value = doze } @VisibleForTesting @@ -642,18 +641,6 @@ constructor( } } - @VisibleForTesting - internal fun listenForDozing(scope: CoroutineScope): Job { - return scope.launch { - combine(keyguardInteractor.dozeAmount, keyguardInteractor.isDozing) { - localDozeAmount, - localIsDozing -> - localDozeAmount > dozeAmount || localIsDozing - } - .collect { localIsDozing -> isDozing = localIsDozing } - } - } - class TimeListener(val clockFace: ClockFaceController, val executor: DelayableExecutor) { val predrawListener = ViewTreeObserver.OnPreDrawListener { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 73dc28230e65..e2f3955263a1 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -172,6 +172,7 @@ public class KeyguardSecurityContainer extends ConstraintLayout { private boolean mIsDragging; private float mStartTouchY = -1; private boolean mDisappearAnimRunning; + private boolean mIsAppearAnimationDelayed; private SwipeListener mSwipeListener; private ViewMode mViewMode = new DefaultViewMode(); private boolean mIsInteractable; @@ -583,6 +584,10 @@ public class KeyguardSecurityContainer extends ConstraintLayout { return false; } + boolean isAppearAnimationDelayed() { + return mIsAppearAnimationDelayed; + } + void addMotionEventListener(Gefingerpoken listener) { mMotionEventListeners.add(listener); } @@ -624,6 +629,19 @@ public class KeyguardSecurityContainer extends ConstraintLayout { mViewMode.startAppearAnimation(securityMode); } + /** + * Set view translationY and alpha as we delay bouncer animation. + */ + public void setupForDelayedAppear() { + setTranslationY(0f); + setAlpha(0f); + setIsAppearAnimationDelayed(true); + } + + public void setIsAppearAnimationDelayed(boolean isDelayed) { + mIsAppearAnimationDelayed = isDelayed; + } + private void beginJankInstrument(int cuj) { KeyguardInputView securityView = mSecurityViewFlipper.getSecurityView(); if (securityView == null) return; @@ -812,6 +830,7 @@ public class KeyguardSecurityContainer extends ConstraintLayout { public void reset() { mViewMode.reset(); mDisappearAnimRunning = false; + mIsAppearAnimationDelayed = false; } /** diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index d10fce416150..198c1cb08647 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -18,6 +18,7 @@ package com.android.keyguard; import static android.app.StatusBarManager.SESSION_KEYGUARD; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISSIBLE_KEYGUARD; import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC; @@ -385,6 +386,10 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard boolean useSplitBouncer = orientation == ORIENTATION_LANDSCAPE; mSecurityViewFlipperController.updateConstraints(useSplitBouncer); } + if (orientation == ORIENTATION_PORTRAIT) { + // If there is any delayed bouncer appear animation it can start now + startAppearAnimationIfDelayed(); + } } @Override @@ -845,6 +850,16 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } } + /** Start appear animation which was previously delayed from opening bouncer in landscape. */ + public void startAppearAnimationIfDelayed() { + if (!mView.isAppearAnimationDelayed()) { + return; + } + setAlpha(1f); + appear(); + mView.setIsAppearAnimationDelayed(false); + } + /** Called when the bouncer changes visibility. */ public void onBouncerVisibilityChanged(boolean isVisible) { if (!isVisible) { @@ -1301,4 +1316,13 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard setAlpha(MathUtils.constrain(1 - scaledFraction, 0f, 1f)); mView.setTranslationY(scaledFraction * mTranslationY); } + + /** Set up view for delayed appear animation. */ + public void setupForDelayedAppear() { + mView.setupForDelayedAppear(); + } + + public boolean isLandscapeOrientation() { + return mLastOrientation == Configuration.ORIENTATION_LANDSCAPE; + } } diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt index d8e7a168ef3c..97de78c41af7 100644 --- a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt @@ -18,6 +18,7 @@ package com.android.systemui.ambient.touch import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator +import android.content.res.Configuration import android.graphics.Rect import android.graphics.Region import android.util.Log @@ -36,6 +37,7 @@ import com.android.systemui.ambient.touch.dagger.BouncerSwipeModule import com.android.systemui.ambient.touch.scrim.ScrimController import com.android.systemui.ambient.touch.scrim.ScrimManager import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants +import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.plugins.ActivityStarter @@ -46,6 +48,7 @@ import com.android.systemui.shade.ShadeExpansionChangeEvent import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.phone.CentralSurfaces +import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.wm.shell.animation.FlingAnimationUtils import java.util.Optional import javax.inject.Inject @@ -82,6 +85,8 @@ constructor( private val sceneInteractor: SceneInteractor, private val shadeRepository: ShadeRepository, private val windowRootViewProvider: Optional<Provider<WindowRootView>>, + private val keyguardStateController: KeyguardStateController, + communalSettingsInteractor: CommunalSettingsInteractor, ) : TouchHandler { /** An interface for creating ValueAnimators. */ interface ValueAnimatorCreator { @@ -101,6 +106,8 @@ constructor( private var capture: Boolean? = null private var expanded: Boolean = false private var touchSession: TouchSession? = null + private var isUserTrackingExpansionDisabled: Boolean = false + private var isKeyguardScreenRotationAllowed: Boolean = false private val scrimManagerCallback = ScrimManager.Callback { controller -> currentScrimController?.reset() @@ -121,6 +128,9 @@ constructor( distanceX: Float, distanceY: Float, ): Boolean { + val isLandscape = + windowRootView.resources.configuration.orientation == + Configuration.ORIENTATION_LANDSCAPE if (capture == null) { capture = if (Flags.dreamOverlayBouncerSwipeDirectionFiltering()) { @@ -137,7 +147,9 @@ constructor( // reset expanding expanded = false // Since the user is dragging the bouncer up, set scrimmed to false. - currentScrimController?.show() + if (isKeyguardScreenRotationAllowed || !isLandscape) { + currentScrimController?.show(false) + } if (SceneContainerFlag.isEnabled) { sceneInteractor.onRemoteUserInputStarted("bouncer touch handler") @@ -172,6 +184,37 @@ constructor( return true } + if (touchSession == null) { + return true + } + val screenTravelPercentage = + (abs((y - e2.y).toDouble()) / touchSession!!.bounds.height()).toFloat() + + if (communalSettingsInteractor.isV2FlagEnabled()) { + if (isUserTrackingExpansionDisabled) return true + // scrolling up in landscape orientation but device doesn't allow keyguard + // screen rotation + if (y > e2.y && !isKeyguardScreenRotationAllowed && isLandscape) { + velocityTracker!!.computeCurrentVelocity(1000) + currentExpansion = 1 - screenTravelPercentage + expanded = + shouldExpandBouncer( + velocityTracker!!.yVelocity, + velocityTracker!!.xVelocity, + EXPANSION_FROM_LANDSCAPE_THRESHOLD, + currentExpansion, + ) + if (expanded) { + // Once scroll past the percentage threshold, show bouncer scrimmed, + // so that user won't be required to drag up and then right to keep + // bouncer open after screen rotates to portrait. + currentScrimController?.show(true) + isUserTrackingExpansionDisabled = true + } + return true + } + } + if (SceneContainerFlag.isEnabled) { windowRootView.dispatchTouchEvent(e2) } else { @@ -182,12 +225,7 @@ constructor( // is fully hidden at full expansion (1) and fully visible when fully // collapsed // (0). - touchSession?.apply { - val screenTravelPercentage = - (abs((this@outer.y - e2.y).toDouble()) / getBounds().height()) - .toFloat() - setPanelExpansion(1 - screenTravelPercentage) - } + touchSession?.apply { setPanelExpansion(1 - screenTravelPercentage) } } } @@ -262,6 +300,7 @@ constructor( } scrimManager.addCallback(scrimManagerCallback) currentScrimController = scrimManager.currentController + isKeyguardScreenRotationAllowed = keyguardStateController.isKeyguardScreenRotationAllowed() shadeRepository.setLegacyShadeTracking(true) session.registerCallback { @@ -271,6 +310,7 @@ constructor( scrimManager.removeCallback(scrimManagerCallback) capture = null touchSession = null + isUserTrackingExpansionDisabled = false if (!Flags.communalBouncerDoNotModifyPluginOpen()) { notificationShadeWindowController.setForcePluginOpen(false, this) } @@ -299,14 +339,25 @@ constructor( return } + // We are already in progress of opening bouncer scrimmed + if (isUserTrackingExpansionDisabled) { + // User is done scrolling, reset + isUserTrackingExpansionDisabled = false + return + } + // We must capture the resulting velocities as resetMonitor() will clear these // values. velocityTracker!!.computeCurrentVelocity(1000) val verticalVelocity = velocityTracker!!.yVelocity - val horizontalVelocity = velocityTracker!!.xVelocity - val velocityVector = - hypot(horizontalVelocity.toDouble(), verticalVelocity.toDouble()).toFloat() - expanded = !flingRevealsOverlay(verticalVelocity, velocityVector) + expanded = + shouldExpandBouncer( + verticalVelocity, + velocityTracker!!.xVelocity, + FLING_PERCENTAGE_THRESHOLD, + currentExpansion, + ) + val expansion = if (expanded!!) KeyguardBouncerConstants.EXPANSION_VISIBLE else KeyguardBouncerConstants.EXPANSION_HIDDEN @@ -339,11 +390,27 @@ constructor( return animator } - protected fun flingRevealsOverlay(velocity: Float, velocityVector: Float): Boolean { + private fun shouldExpandBouncer( + verticalVelocity: Float, + horizontalVelocity: Float, + threshold: Float, + expansion: Float, + ): Boolean { + val velocityVector = + hypot(horizontalVelocity.toDouble(), verticalVelocity.toDouble()).toFloat() + return !flingRevealsOverlay(verticalVelocity, velocityVector, threshold, expansion) + } + + protected fun flingRevealsOverlay( + velocity: Float, + velocityVector: Float, + threshold: Float, + expansion: Float, + ): Boolean { // Fully expand the space above the bouncer, if the user has expanded the bouncer less // than halfway or final velocity was positive, indicating a downward direction. return if (abs(velocityVector.toDouble()) < flingAnimationUtils.minVelocityPxPerSecond) { - currentExpansion > FLING_PERCENTAGE_THRESHOLD + expansion > threshold } else { velocity > 0 } @@ -390,6 +457,7 @@ constructor( companion object { const val FLING_PERCENTAGE_THRESHOLD = 0.5f + const val EXPANSION_FROM_LANDSCAPE_THRESHOLD = 0.95f private const val TAG = "BouncerSwipeTouchHandler" } } diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java index 94c998267598..6f2dd799c409 100644 --- a/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java @@ -33,8 +33,8 @@ public class BouncerScrimController implements ScrimController { } @Override - public void show() { - mStatusBarKeyguardViewManager.showPrimaryBouncer(false); + public void show(boolean scrimmed) { + mStatusBarKeyguardViewManager.showPrimaryBouncer(scrimmed); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java index 00543523ec2e..90cbd258f03e 100644 --- a/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java @@ -25,8 +25,9 @@ import com.android.systemui.shade.ShadeExpansionChangeEvent; public interface ScrimController { /** * Called at the start of expansion before any expansion amount updates. + * @param scrimmed true when the bouncer should show scrimmed, false when user will be dragging. */ - default void show() { + default void show(boolean scrimmed) { } /** 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 bbf9a19012a4..30b98a658821 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 @@ -137,6 +137,9 @@ constructor( ) bind(overlayView!!, overlayViewModel, windowManager.get()) overlayView!!.visibility = View.INVISIBLE + overlayView!!.setOnClickListener { v -> + v.requireViewById<LottieAnimationView>(R.id.sidefps_animation).toggleAnimation() + } Log.d(TAG, "show(): adding overlayView $overlayView") windowManager.get().addView(overlayView, overlayViewModel.defaultOverlayViewParams) } @@ -234,3 +237,11 @@ private fun LottieAnimationView.addOverlayDynamicColor(colorCallbacks: List<Lott resumeAnimation() } } + +fun LottieAnimationView.toggleAnimation() { + if (isAnimating) { + pauseAnimation() + } else { + resumeAnimation() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt index 75503e8575f0..b26a2c08532e 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt @@ -17,7 +17,9 @@ package com.android.systemui.bouncer.domain.interactor import android.app.StatusBarManager.SESSION_KEYGUARD +import com.android.app.tracing.FlowTracing.traceAsCounter import com.android.app.tracing.coroutines.asyncTraced as async +import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.internal.logging.UiEventLogger import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor @@ -38,9 +40,12 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInt import com.android.systemui.log.SessionTracker import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.scene.domain.interactor.SceneBackInteractor +import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.ShadeDisplayAware +import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow @@ -49,7 +54,9 @@ import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map /** Encapsulates business logic and application state accessing use-cases. */ @@ -65,6 +72,7 @@ constructor( private val powerInteractor: PowerInteractor, private val uiEventLogger: UiEventLogger, private val sessionTracker: SessionTracker, + sceneInteractor: SceneInteractor, sceneBackInteractor: SceneBackInteractor, @ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor, ) { @@ -149,6 +157,31 @@ constructor( val dismissDestination: Flow<SceneKey> = sceneBackInteractor.backScene.map { it ?: Scenes.Lockscreen } + /** The amount [0-1] that the Bouncer Overlay has been transitioned to. */ + val bouncerExpansion: Flow<Float> = + if (SceneContainerFlag.isEnabled) { + sceneInteractor.transitionState.flatMapLatestConflated { state -> + when (state) { + is ObservableTransitionState.Idle -> + flowOf(if (Overlays.Bouncer in state.currentOverlays) 1f else 0f) + is ObservableTransitionState.Transition -> + if (state.toContent == Overlays.Bouncer) { + state.progress + } else if (state.fromContent == Overlays.Bouncer) { + state.progress.map { progress -> 1 - progress } + } else { + state.currentOverlays().map { + if (Overlays.Bouncer in it) 1f else 0f + } + } + } + } + } else { + flowOf() + } + .distinctUntilChanged() + .traceAsCounter("bouncer_expansion") { (it * 100f).toInt() } + /** Notifies that the user has places down a pointer, not necessarily dragging just yet. */ fun onDown() { falsingInteractor.avoidGesture() diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt index 7f268315e566..5d64219c7f90 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt @@ -15,6 +15,7 @@ import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToPrimaryBouncerTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel import com.android.systemui.log.BouncerLogger import com.android.systemui.user.domain.interactor.SelectedUserInteractor @@ -30,6 +31,8 @@ data class LegacyBouncerDependencies constructor( val viewModel: KeyguardBouncerViewModel, val primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel, + val glanceableHubToPrimaryBouncerTransitionViewModel: + GlanceableHubToPrimaryBouncerTransitionViewModel, val componentFactory: KeyguardBouncerComponent.Factory, val messageAreaControllerFactory: KeyguardMessageAreaController.Factory, val bouncerMessageInteractor: BouncerMessageInteractor, @@ -82,6 +85,7 @@ constructor( view, deps.viewModel, deps.primaryBouncerToGoneTransitionViewModel, + deps.glanceableHubToPrimaryBouncerTransitionViewModel, deps.componentFactory, deps.messageAreaControllerFactory, deps.bouncerMessageInteractor, diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt index 7d8945a5b4a7..45f0e13c185e 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt @@ -33,6 +33,7 @@ import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE import com.android.systemui.bouncer.ui.BouncerViewDelegate import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel +import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToPrimaryBouncerTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.log.BouncerLogger @@ -49,6 +50,8 @@ object KeyguardBouncerViewBinder { view: ViewGroup, viewModel: KeyguardBouncerViewModel, primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel, + glanceableHubToPrimaryBouncerTransitionViewModel: + GlanceableHubToPrimaryBouncerTransitionViewModel, componentFactory: KeyguardBouncerComponent.Factory, messageAreaControllerFactory: KeyguardMessageAreaController.Factory, bouncerMessageInteractor: BouncerMessageInteractor, @@ -133,7 +136,20 @@ object KeyguardBouncerViewBinder { /* turningOff= */ false ) securityContainerController.setInitialMessage() - securityContainerController.appear() + // Delay bouncer appearing animation when opening it from the + // glanceable hub in landscape, until after orientation changes + // to portrait. This prevents bouncer from showing in landscape + // layout, if bouncer rotation is not allowed. + if ( + glanceableHubToPrimaryBouncerTransitionViewModel + .willDelayAppearAnimation( + securityContainerController.isLandscapeOrientation + ) + ) { + securityContainerController.setupForDelayedAppear() + } else { + securityContainerController.appear() + } securityContainerController.onResume( KeyguardSecurityView.SCREEN_ON ) diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt index e36e85565293..49b0bb63545f 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt @@ -29,10 +29,17 @@ import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalScenes.isCommunal import com.android.systemui.communal.shared.model.CommunalTransitionKeys 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.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING +import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.NotificationShadeWindowController +import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf import com.android.systemui.util.kotlin.emitOnStart import com.android.systemui.util.kotlin.sample import com.android.systemui.util.settings.SettingsProxyExt.observerFlow @@ -45,6 +52,7 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.withContext @@ -60,10 +68,12 @@ constructor( private val communalInteractor: CommunalInteractor, private val communalSettingsInteractor: CommunalSettingsInteractor, private val communalSceneInteractor: CommunalSceneInteractor, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val keyguardInteractor: KeyguardInteractor, private val systemSettings: SystemSettings, private val notificationShadeWindowController: NotificationShadeWindowController, @Background private val bgScope: CoroutineScope, + @Application private val applicationScope: CoroutineScope, @Main private val mainDispatcher: CoroutineDispatcher, private val uiEventLogger: UiEventLogger, ) : CoreStartable { @@ -154,6 +164,25 @@ constructor( } } } + + if (communalSettingsInteractor.isV2FlagEnabled()) { + applicationScope.launch(context = mainDispatcher) { + anyOf( + communalSceneInteractor.isTransitioningToOrIdleOnCommunal, + // when transitioning from hub to dream, allow hub to stay at the current + // orientation, as keyguard doesn't allow rotation by default. + keyguardTransitionInteractor.isInTransition( + edge = Edge.create(from = Scenes.Communal, to = DREAMING), + edgeWithoutSceneContainer = + Edge.create(from = GLANCEABLE_HUB, to = DREAMING), + ), + ) + .distinctUntilChanged() + .collectLatest { + notificationShadeWindowController.setGlanceableHubOrientationAware(it) + } + } + } } private fun cancelHubTimeout() { diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt index 3d9e93036dbc..fed99d71fa3b 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt @@ -307,6 +307,21 @@ constructor( initialValue = false, ) + /** Flow that emits a boolean if transitioning to or idle on communal scene. */ + val isTransitioningToOrIdleOnCommunal: Flow<Boolean> = + transitionState + .map { + (it is ObservableTransitionState.Idle && + it.currentScene == CommunalScenes.Communal) || + (it is ObservableTransitionState.Transition && + it.toContent == CommunalScenes.Communal) + } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = false, + ) + private companion object { const val TAG = "CommunalSceneInteractor" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt index 63cf4f72e415..ab0efed2cb76 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt @@ -85,7 +85,7 @@ constructor( val previewClock: Flow<ClockController> = keyguardClockRepository.previewClock - val clockEventController: ClockEventController by keyguardClockRepository::clockEventController + val clockEventController: ClockEventController = keyguardClockRepository.clockEventController var clock: ClockController? by keyguardClockRepository.clockEventController::clock diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt index def1ac8742da..e81d5354ec6e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt @@ -81,22 +81,92 @@ object KeyguardSmartspaceViewBinder { } if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) { + val xBuffer = + keyguardRootView.context.resources.getDimensionPixelSize( + R.dimen.smartspace_padding_horizontal + ) + val yBuffer = + keyguardRootView.context.resources.getDimensionPixelSize( + R.dimen.smartspace_padding_vertical + ) + + val smallViewIds = + listOf(sharedR.id.date_smartspace_view, sharedR.id.weather_smartspace_view) + + val largeViewIds = + listOf( + sharedR.id.date_smartspace_view_large, + sharedR.id.weather_smartspace_view_large, + ) + launch("$TAG#smartspaceViewModel.burnInLayerVisibility") { - keyguardRootViewModel.burnInLayerVisibility.collect { visibility -> - if (clockViewModel.isLargeClockVisible.value) { - // hide small clock date/weather - val dateView = - keyguardRootView.requireViewById<View>( - sharedR.id.date_smartspace_view - ) - dateView.visibility = View.GONE - val weatherView = - keyguardRootView.requireViewById<View>( - sharedR.id.weather_smartspace_view - ) - weatherView.visibility = View.GONE + combine( + keyguardRootViewModel.burnInLayerVisibility, + clockViewModel.isLargeClockVisible, + ::Pair, + ) + .collect { (visibility, isLargeClock) -> + if (isLargeClock) { + // hide small clock date/weather + for (viewId in smallViewIds) { + keyguardRootView.findViewById<View>(viewId)?.let { + it.visibility = View.GONE + } + } + } + } + } + + launch("$TAG#clockEventController.onClockBoundsChanged") { + // Whenever the doze amount changes, the clock may update it's view bounds. + // We need to update our layout position as a result. We could do this via + // `requestLayout`, but that's quite expensive when enclosed in since this + // recomputes the entire ConstraintLayout, so instead we do it manually. We + // would use translationX/Y for this, but that's used by burnin. + combine( + clockViewModel.isLargeClockVisible, + clockViewModel.clockEventController.onClockBoundsChanged, + ::Pair, + ) + .collect { (isLargeClock, clockBounds) -> + for (id in (if (isLargeClock) smallViewIds else largeViewIds)) { + keyguardRootView.findViewById<View>(id)?.let { + it.visibility = View.GONE + } + } + + if (clockBounds == null) return@collect + if (isLargeClock) { + val largeDateHeight = + keyguardRootView + .findViewById<View>( + sharedR.id.date_smartspace_view_large + ) + ?.height ?: 0 + for (id in largeViewIds) { + keyguardRootView.findViewById<View>(id)?.let { view -> + val viewHeight = view.height + val offset = (largeDateHeight - viewHeight) / 2 + view.top = + (clockBounds.bottom + yBuffer + offset).toInt() + view.bottom = view.top + viewHeight + } + } + } else { + for (id in smallViewIds) { + keyguardRootView.findViewById<View>(id)?.let { view -> + val viewWidth = view.width + if (view.isLayoutRtl()) { + view.right = (clockBounds.left - xBuffer).toInt() + view.left = view.right - viewWidth + } else { + view.left = (clockBounds.right + xBuffer).toInt() + view.right = view.left + viewWidth + } + } + } + } } - } } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt index 37cc852ffe00..d0b5f743c277 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt @@ -226,7 +226,7 @@ constructor( ConstraintSet.TOP, customR.id.lockscreen_clock_view_large, ConstraintSet.BOTTOM, - dateWeatherPaddingStart, + context.resources.getDimensionPixelSize(R.dimen.smartspace_padding_vertical), ) connect( @@ -291,7 +291,9 @@ constructor( ConstraintSet.START, customR.id.lockscreen_clock_view, ConstraintSet.END, - 20, + context.resources.getDimensionPixelSize( + R.dimen.smartspace_padding_horizontal + ), ) connect( sharedR.id.date_smartspace_view, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModel.kt index 40010548a268..c088900f9304 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModel.kt @@ -17,33 +17,39 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.Flags +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.communal.shared.model.CommunalBackgroundType import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.FromGlanceableHubTransitionInteractor import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.BlurConfig import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition +import com.android.systemui.statusbar.policy.KeyguardStateController import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest +@OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class GlanceableHubToPrimaryBouncerTransitionViewModel @Inject constructor( private val blurConfig: BlurConfig, animationFlow: KeyguardTransitionAnimationFlow, - communalSettingsInteractor: CommunalSettingsInteractor, + private val communalSettingsInteractor: CommunalSettingsInteractor, + private val communalSceneInteractor: CommunalSceneInteractor, + private val keyguardStateController: KeyguardStateController, ) : PrimaryBouncerTransition { private val transitionAnimation = animationFlow .setup( - duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION, + duration = FromGlanceableHubTransitionInteractor.TO_BOUNCER_DURATION, edge = Edge.INVALID, ) .setupWithoutSceneContainer(edge = Edge.create(GLANCEABLE_HUB, PRIMARY_BOUNCER)) @@ -59,6 +65,13 @@ constructor( transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx) } + /** Whether to delay the animation to fade in bouncer elements. */ + fun willDelayAppearAnimation(isLandscape: Boolean): Boolean = + communalSettingsInteractor.isV2FlagEnabled() && + communalSceneInteractor.isIdleOnCommunal.value && + !keyguardStateController.isKeyguardScreenRotationAllowed() && + isLandscape + override val notificationBlurRadius: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0.0f) } 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 cf5cc264be8d..dcbf7b5a9335 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 @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.ui.viewmodel import android.content.Context import android.content.res.Resources import androidx.constraintlayout.helper.widget.Layer +import com.android.keyguard.ClockEventController import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.customization.R as customR import com.android.systemui.dagger.SysUISingleton @@ -68,6 +69,7 @@ constructor( initialValue = true, ) + val clockEventController: ClockEventController = keyguardClockInteractor.clockEventController val currentClock = keyguardClockInteractor.currentClock val hasCustomWeatherDataDisplay = diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java index e4cd7ea098af..305444f7ab5e 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java @@ -451,6 +451,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW } else { mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; } + } else if (state.glanceableHubOrientationAware) { + mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER; } else { mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; } @@ -627,6 +629,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW state.shadeOrQsExpanded, state.notificationShadeFocusable, state.glanceableHubShowing, + state.glanceableHubOrientationAware, state.bouncerShowing, state.keyguardFadingAway, state.keyguardGoingAway, @@ -763,6 +766,12 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW } @Override + public void setGlanceableHubOrientationAware(boolean isOrientationAware) { + mCurrentState.glanceableHubOrientationAware = isOrientationAware; + apply(mCurrentState); + } + + @Override public void setBackdropShowing(boolean showing) { mCurrentState.mediaBackdropShowing = showing; apply(mCurrentState); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt index 6a4b52af498c..a1eac745b3a1 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt @@ -36,6 +36,7 @@ class NotificationShadeWindowState( @JvmField var notificationShadeFocusable: Boolean = false, @JvmField var bouncerShowing: Boolean = false, @JvmField var glanceableHubShowing: Boolean = false, + @JvmField var glanceableHubOrientationAware: Boolean = false, @JvmField var keyguardFadingAway: Boolean = false, @JvmField var keyguardGoingAway: Boolean = false, @JvmField var qsExpanded: Boolean = false, @@ -81,6 +82,7 @@ class NotificationShadeWindowState( notificationShadeFocusable.toString(), bouncerShowing.toString(), glanceableHubShowing.toString(), + glanceableHubOrientationAware.toString(), keyguardFadingAway.toString(), keyguardGoingAway.toString(), qsExpanded.toString(), @@ -122,6 +124,7 @@ class NotificationShadeWindowState( panelExpanded: Boolean, notificationShadeFocusable: Boolean, glanceableHubShowing: Boolean, + glanceableHubOrientationAware: Boolean, bouncerShowing: Boolean, keyguardFadingAway: Boolean, keyguardGoingAway: Boolean, @@ -153,6 +156,7 @@ class NotificationShadeWindowState( this.shadeOrQsExpanded = panelExpanded this.notificationShadeFocusable = notificationShadeFocusable this.glanceableHubShowing = glanceableHubShowing + this.glanceableHubOrientationAware = glanceableHubOrientationAware this.bouncerShowing = bouncerShowing this.keyguardFadingAway = keyguardFadingAway this.keyguardGoingAway = keyguardGoingAway @@ -202,6 +206,7 @@ class NotificationShadeWindowState( "panelExpanded", "notificationShadeFocusable", "glanceableHubShowing", + "glanceableHubOrientationAware", "bouncerShowing", "keyguardFadingAway", "keyguardGoingAway", @@ -223,7 +228,7 @@ class NotificationShadeWindowState( "dozing", "scrimsVisibility", "backgroundBlurRadius", - "communalVisible" + "communalVisible", ) } } 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 246177e0c46d..81146852aefc 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 @@ -334,7 +334,7 @@ constructor( } else if (state.fromContent == overlay) { state.progress.map { progress -> 1 - progress } } else { - flowOf(0f) + state.currentOverlays().map { if (overlay in it) 1f else 0f } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java index 85fad420daf1..50cf015af5e3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java @@ -89,6 +89,9 @@ public interface NotificationShadeWindowController extends RemoteInputController /** Sets the state of whether the glanceable hub is showing or not. */ default void setGlanceableHubShowing(boolean showing) {} + /** Sets the state of whether the glanceable hub can change with user's orientation or not. */ + default void setGlanceableHubOrientationAware(boolean isOrientationAware) {} + /** Sets the state of whether the backdrop is showing or not. */ default void setBackdropShowing(boolean showing) {} 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 4390f1b16ffd..1a17b8efb4ae 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 @@ -1226,6 +1226,14 @@ public class NotificationStackScrollLayout } @Override + public void setOccluded(boolean isOccluded) { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) { + return; + } + this.setVisibility(isOccluded ? View.INVISIBLE : View.VISIBLE); + } + + @Override public void setScrollState(@NonNull ShadeScrollState scrollState) { if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) { return; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt index a7305f7f27ab..9c855e9cd9b7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt @@ -49,6 +49,9 @@ interface NotificationScrollView { /** Max alpha for this view */ fun setMaxAlpha(alpha: Float) + /** Set whether this view is occluded by something else. */ + fun setOccluded(isOccluded: Boolean) + /** Sets a clipping shape, which defines the drawable area of this view. */ fun setClippingShape(shape: ShadeScrimShape?) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt index a4e39cbd8388..653344ae9203 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt @@ -86,6 +86,8 @@ constructor( .collectTraced { view.setClippingShape(it) } } + launch { viewModel.isOccluded.collectTraced { view.setOccluded(it) } } + launch { viewModel.maxAlpha.collectTraced { view.setMaxAlpha(it) } } launch { viewModel.shadeScrollState.collect { view.setScrollState(it) } } launch { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt index 1dbaf2f0f401..a277597e23df 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt @@ -24,7 +24,9 @@ import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.ObservableTransitionState.Idle import com.android.compose.animation.scene.ObservableTransitionState.Transition import com.android.compose.animation.scene.ObservableTransitionState.Transition.ChangeScene +import com.android.compose.animation.scene.OverlayKey import com.android.compose.animation.scene.SceneKey +import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.lifecycle.ExclusiveActivatable @@ -70,6 +72,7 @@ constructor( private val stackAppearanceInteractor: NotificationStackAppearanceInteractor, shadeInteractor: ShadeInteractor, shadeModeInteractor: ShadeModeInteractor, + bouncerInteractor: BouncerInteractor, private val remoteInputInteractor: RemoteInputInteractor, private val sceneInteractor: SceneInteractor, // TODO(b/336364825) Remove Lazy when SceneContainerFlag is released - @@ -131,12 +134,15 @@ constructor( private fun expandFractionDuringOverlayTransition( transition: Transition, currentScene: SceneKey, + currentOverlays: Set<OverlayKey>, shadeExpansion: Float, ): Float { return if (currentScene == Scenes.Lockscreen) { 1f } else if (transition.isTransitioningFromOrTo(Overlays.NotificationsShade)) { shadeExpansion + } else if (Overlays.NotificationsShade in currentOverlays) { + 1f } else { 0f } @@ -161,12 +167,13 @@ constructor( shadeInteractor.qsExpansion, shadeModeInteractor.shadeMode, sceneInteractor.transitionState, - ) { shadeExpansion, qsExpansion, _, transitionState -> + sceneInteractor.currentOverlays, + ) { shadeExpansion, qsExpansion, _, transitionState, currentOverlays -> when (transitionState) { is Idle -> if ( expandedInScene(transitionState.currentScene) || - Overlays.NotificationsShade in transitionState.currentOverlays + Overlays.NotificationsShade in currentOverlays ) { 1f } else { @@ -182,12 +189,14 @@ constructor( expandFractionDuringOverlayTransition( transition = transitionState, currentScene = transitionState.currentScene, + currentOverlays = currentOverlays, shadeExpansion = shadeExpansion, ) is Transition.ReplaceOverlay -> expandFractionDuringOverlayTransition( transition = transitionState, currentScene = transitionState.currentScene, + currentOverlays = currentOverlays, shadeExpansion = shadeExpansion, ) } @@ -198,6 +207,12 @@ constructor( val qsExpandFraction: Flow<Float> = shadeInteractor.qsExpansion.dumpWhileCollecting("qsExpandFraction") + val isOccluded: Flow<Boolean> = + bouncerInteractor.bouncerExpansion + .map { it == 1f } + .distinctUntilChanged() + .dumpWhileCollecting("isOccluded") + /** Blur radius to be applied to Notifications. */ fun blurRadius(maxBlurRadius: Flow<Int>) = combine(blurFraction, maxBlurRadius) { fraction, maxRadius -> fraction * maxRadius } 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 33cc62c9a75a..9d55e1d9d592 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 @@ -21,6 +21,7 @@ import android.content.Context import androidx.annotation.VisibleForTesting import com.android.app.tracing.coroutines.flow.flowName import com.android.systemui.Flags.glanceableHubV2 +import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor @@ -106,7 +107,6 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combineTransform import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest @@ -132,6 +132,7 @@ constructor( private val keyguardInteractor: KeyguardInteractor, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val shadeInteractor: ShadeInteractor, + private val bouncerInteractor: BouncerInteractor, shadeModeInteractor: ShadeModeInteractor, notificationStackAppearanceInteractor: NotificationStackAppearanceInteractor, private val alternateBouncerToGoneTransitionViewModel: @@ -516,8 +517,13 @@ constructor( combineTransform( shadeInteractor.shadeExpansion, shadeInteractor.qsExpansion, - ) { shadeExpansion, qsExpansion -> - if (qsExpansion == 1f) { + bouncerInteractor.bouncerExpansion, + ) { shadeExpansion, qsExpansion, bouncerExpansion -> + if (bouncerExpansion == 1f) { + emit(0f) + } else if (bouncerExpansion > 0f) { + emit(1 - bouncerExpansion) + } else if (qsExpansion == 1f) { // Ensure HUNs will be visible in QS shade (at least while // unlocked) emit(1f) @@ -526,19 +532,36 @@ constructor( emit(1f - qsExpansion) } } - Split -> isAnyExpanded.filter { it }.map { 1f } + Split -> + combineTransform(isAnyExpanded, bouncerInteractor.bouncerExpansion) { + isAnyExpanded, + bouncerExpansion -> + if (bouncerExpansion == 1f) { + emit(0f) + } else if (bouncerExpansion > 0f) { + emit(1 - bouncerExpansion) + } else if (isAnyExpanded) { + emit(1f) + } + } Dual -> combineTransform( shadeModeInteractor.isShadeLayoutWide, headsUpNotificationInteractor.get().isHeadsUpOrAnimatingAway, shadeInteractor.shadeExpansion, shadeInteractor.qsExpansion, + bouncerInteractor.bouncerExpansion, ) { isShadeLayoutWide, isHeadsUpOrAnimatingAway, shadeExpansion, - qsExpansion -> - if (isShadeLayoutWide) { + qsExpansion, + bouncerExpansion -> + if (bouncerExpansion == 1f) { + emit(0f) + } else if (bouncerExpansion > 0f) { + emit(1 - bouncerExpansion) + } else if (isShadeLayoutWide) { if (shadeExpansion > 0f) { emit(1f) } diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt index 33e1929ebf8b..952d40ed63eb 100644 --- a/packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt +++ b/packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt @@ -17,8 +17,13 @@ package com.android.systemui.wallpapers import android.app.Flags +import android.content.res.Configuration.UI_MODE_NIGHT_MASK +import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.graphics.Canvas +import android.graphics.Color import android.graphics.Paint +import android.graphics.PorterDuff +import android.graphics.PorterDuffXfermode import android.graphics.RadialGradient import android.graphics.Shader import android.service.wallpaper.WallpaperService @@ -74,9 +79,9 @@ class GradientColorWallpaper : WallpaperService() { .toFloat() val totalHeight = destRectF.height() + (offsetPx * 2) val leftCenterX = -offsetPx - val leftCenterY = -offsetPx + val leftCenterY = totalHeight - offsetPx val rightCenterX = offsetPx + destRectF.width() - val rightCenterY = totalHeight - offsetPx + val rightCenterY = -offsetPx val radius = (destRectF.width() / 2) + offsetPx canvas.drawCircle( @@ -112,6 +117,28 @@ class GradientColorWallpaper : WallpaperService() { ) }, ) + + val isDarkMode = + context.resources.configuration.uiMode and UI_MODE_NIGHT_MASK == + UI_MODE_NIGHT_YES + val maskColor = + ColorUtils.setAlphaComponent( + if (isDarkMode) Color.BLACK else Color.WHITE, + /* alpha= */ 87, // 0.34f * 255 + ) + val maskPaint = + Paint().apply { + xfermode = + PorterDuffXfermode( + if (isDarkMode) { + PorterDuff.Mode.DARKEN + } else { + PorterDuff.Mode.LIGHTEN + } + ) + color = maskColor + } + canvas.drawRect(destRectF, maskPaint) } catch (exception: IllegalStateException) { Log.d(TAG, "Fail to draw in the canvas", exception) } finally { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index f822ee97807e..f18d73d57f83 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -194,7 +194,7 @@ class ClockEventControllerTest : SysuiTestCase() { @Test fun clockSet_validateInitialization() { - verify(clock).initialize(any(), anyFloat(), anyFloat()) + verify(clock).initialize(any(), anyFloat(), anyFloat(), any()) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt index b4fbaad6ab37..5f3442048fcd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt @@ -18,7 +18,6 @@ package com.android.systemui.wallpapers import android.app.Flags import android.content.Context -import android.content.res.Resources import android.graphics.Canvas import android.graphics.Paint import android.graphics.Rect @@ -43,6 +42,7 @@ import org.mockito.Mock import org.mockito.Mockito.spy import org.mockito.MockitoAnnotations import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.verifyZeroInteractions @@ -61,23 +61,20 @@ class GradientColorWallpaperTest : SysuiTestCase() { @Mock private lateinit var mockContext: Context - @Mock private lateinit var mockResources: Resources - @Before fun setUp() { MockitoAnnotations.initMocks(this) + val spyResources = spy(context.resources) + whenever(surfaceHolder.surface).thenReturn(surface) whenever(surfaceHolder.surfaceFrame).thenReturn(surfaceFrame) whenever(surface.lockHardwareCanvas()).thenReturn(canvas) whenever(mockContext.getColor(anyInt())).thenReturn(1) - whenever(mockContext.resources).thenReturn(mockResources) - whenever( - mockResources.getDimensionPixelOffset( - eq(R.dimen.gradient_color_wallpaper_center_offset) - ) - ) - .thenReturn(OFFSET_PX) + whenever(mockContext.resources).thenReturn(spyResources) + doReturn(OFFSET_PX) + .`when`(spyResources) + .getDimensionPixelOffset(eq(R.dimen.gradient_color_wallpaper_center_offset)) } private fun createGradientColorWallpaperEngine(): Engine { @@ -106,7 +103,8 @@ class GradientColorWallpaperTest : SysuiTestCase() { engine.onSurfaceRedrawNeeded(surfaceHolder) - verify(canvas).drawRect(any<RectF>(), any<Paint>()) + // One rect for the background, one rect for the foreground mask. + verify(canvas, times(2)).drawRect(any<RectF>(), any<Paint>()) verify(canvas, times(2)).drawCircle(anyFloat(), anyFloat(), anyFloat(), any<Paint>()) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt index d27ecce89937..94d27f73aee7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt @@ -28,6 +28,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.log.sessionTracker import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.scene.domain.interactor.sceneBackInteractor +import com.android.systemui.scene.domain.interactor.sceneInteractor val Kosmos.bouncerInteractor by Fixture { BouncerInteractor( @@ -39,6 +40,7 @@ val Kosmos.bouncerInteractor by Fixture { powerInteractor = powerInteractor, uiEventLogger = uiEventLogger, sessionTracker = sessionTracker, + sceneInteractor = sceneInteractor, sceneBackInteractor = sceneBackInteractor, configurationInteractor = configurationInteractor, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelKosmos.kt index b233d3ff9e0f..c6f55f053d35 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModelKosmos.kt @@ -16,16 +16,20 @@ package com.android.systemui.keyguard.ui.viewmodel +import com.android.systemui.communal.domain.interactor.communalSceneInteractor import com.android.systemui.communal.domain.interactor.communalSettingsInteractor import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.blurConfig import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.statusbar.policy.keyguardStateController val Kosmos.glanceableHubToPrimaryBouncerTransitionViewModel by Fixture { GlanceableHubToPrimaryBouncerTransitionViewModel( animationFlow = keyguardTransitionAnimationFlow, blurConfig = blurConfig, communalSettingsInteractor = communalSettingsInteractor, + communalSceneInteractor = communalSceneInteractor, + keyguardStateController = keyguardStateController, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt index 02cf1f5a7214..dff9f3abfc05 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt @@ -29,6 +29,7 @@ import com.android.systemui.common.ui.domain.interactor.configurationInteractor import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.communalSceneInteractor +import com.android.systemui.communal.domain.interactor.communalSettingsInteractor import com.android.systemui.communal.ui.viewmodel.communalTransitionViewModel import com.android.systemui.concurrency.fakeExecutor import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor @@ -87,6 +88,7 @@ import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.wifiIntera import com.android.systemui.statusbar.policy.configurationController import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor +import com.android.systemui.statusbar.policy.keyguardStateController import com.android.systemui.statusbar.ui.viewmodel.keyguardStatusBarViewModel import com.android.systemui.util.time.systemClock import com.android.systemui.volume.domain.interactor.volumeDialogInteractor @@ -126,6 +128,7 @@ class KosmosJavaAdapter() { val keyguardInteractor by lazy { kosmos.keyguardInteractor } val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository } val keyguardTransitionInteractor by lazy { kosmos.keyguardTransitionInteractor } + val keyguardStateController by lazy { kosmos.keyguardStateController } val keyguardStatusBarViewModel by lazy { kosmos.keyguardStatusBarViewModel } val powerRepository by lazy { kosmos.fakePowerRepository } val clock by lazy { kosmos.systemClock } @@ -147,6 +150,7 @@ class KosmosJavaAdapter() { val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor } val communalInteractor by lazy { kosmos.communalInteractor } val communalSceneInteractor by lazy { kosmos.communalSceneInteractor } + val communalSettingsInteractor by lazy { kosmos.communalSettingsInteractor } val sceneContainerPlugin by lazy { kosmos.sceneContainerPlugin } val deviceProvisioningInteractor by lazy { kosmos.deviceProvisioningInteractor } val fakeDeviceProvisioningRepository by lazy { kosmos.fakeDeviceProvisioningRepository } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt index 167b11da9dba..87ce501b0402 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModelKosmos.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel +import com.android.systemui.bouncer.domain.interactor.bouncerInteractor import com.android.systemui.dump.dumpManager import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.kosmos.Kosmos @@ -32,6 +33,7 @@ val Kosmos.notificationScrollViewModel by Fixture { stackAppearanceInteractor = notificationStackAppearanceInteractor, shadeInteractor = shadeInteractor, shadeModeInteractor = shadeModeInteractor, + bouncerInteractor = bouncerInteractor, remoteInputInteractor = remoteInputInteractor, sceneInteractor = sceneInteractor, keyguardInteractor = { keyguardInteractor }, 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 17ef208fe12e..85fe3d9a44ce 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 @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import android.content.applicationContext +import com.android.systemui.bouncer.domain.interactor.bouncerInteractor import com.android.systemui.common.ui.domain.interactor.configurationInteractor import com.android.systemui.communal.domain.interactor.communalSceneInteractor import com.android.systemui.dump.dumpManager @@ -74,6 +75,7 @@ val Kosmos.sharedNotificationContainerViewModel by Fixture { keyguardInteractor = keyguardInteractor, keyguardTransitionInteractor = keyguardTransitionInteractor, shadeInteractor = shadeInteractor, + bouncerInteractor = bouncerInteractor, shadeModeInteractor = shadeModeInteractor, notificationStackAppearanceInteractor = notificationStackAppearanceInteractor, alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel, diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java index bb3c710b0c23..0f6f86b39458 100644 --- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java +++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java @@ -103,12 +103,16 @@ public class AutoclickController extends BaseEventStreamTransformation { @Override public void toggleAutoclickPause(boolean paused) { if (paused) { - if (mClickScheduler != null) { - mClickScheduler.cancel(); - } - if (mAutoclickIndicatorScheduler != null) { - mAutoclickIndicatorScheduler.cancel(); - } + cancelPendingClick(); + } + } + + @Override + public void onHoverChange(boolean hovered) { + // Cancel all pending clicks when the mouse moves outside the panel while + // autoclick is still paused. + if (!hovered && isPaused()) { + cancelPendingClick(); } } }; @@ -226,8 +230,17 @@ public class AutoclickController extends BaseEventStreamTransformation { } private boolean isPaused() { - // TODO (b/397460424): Unpause when hovering over panel. - return Flags.enableAutoclickIndicator() && mAutoclickTypePanel.isPaused(); + return Flags.enableAutoclickIndicator() && mAutoclickTypePanel.isPaused() + && !mAutoclickTypePanel.isHovered(); + } + + private void cancelPendingClick() { + if (mClickScheduler != null) { + mClickScheduler.cancel(); + } + if (mAutoclickIndicatorScheduler != null) { + mAutoclickIndicatorScheduler.cancel(); + } } @VisibleForTesting diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickLinearLayout.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickLinearLayout.java new file mode 100644 index 000000000000..fe8adf75704d --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickLinearLayout.java @@ -0,0 +1,80 @@ +/* + * Copyright 2025 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.accessibility.autoclick; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.LinearLayout; + +/** + * A custom LinearLayout that provides enhanced hover event handling. + * This class overrides hover methods to track hover events for the entire panel ViewGroup, + * including the descendant buttons. This allows for consistent hover behavior and feedback + * across the entire layout. + */ +public class AutoclickLinearLayout extends LinearLayout { + public interface OnHoverChangedListener { + /** + * Called when the hover state of the AutoclickLinearLayout changes. + * + * @param hovered {@code true} if the view is now hovered, {@code false} otherwise. + */ + void onHoverChanged(boolean hovered); + } + + private OnHoverChangedListener mListener; + + public AutoclickLinearLayout(Context context) { + super(context); + } + + public AutoclickLinearLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public AutoclickLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public AutoclickLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public void setOnHoverChangedListener(OnHoverChangedListener listener) { + mListener = listener; + } + + @Override + public boolean onInterceptHoverEvent(MotionEvent event) { + int action = event.getActionMasked(); + setHovered(action == MotionEvent.ACTION_HOVER_ENTER + || action == MotionEvent.ACTION_HOVER_MOVE); + + return false; + } + + @Override + public void onHoverChanged(boolean hovered) { + super.onHoverChanged(hovered); + + if (mListener != null) { + mListener.onHoverChanged(hovered); + } + } +} diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java index ab4b3b13eece..57bbb4a7a0a7 100644 --- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java +++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java @@ -110,11 +110,18 @@ public class AutoclickTypePanel { * @param paused {@code true} to pause autoclick, {@code false} to resume. */ void toggleAutoclickPause(boolean paused); + + /** + * Called when the hovered state of the panel changes. + * + * @param hovered {@code true} if the panel is now hovered, {@code false} otherwise. + */ + void onHoverChange(boolean hovered); } private final Context mContext; - private final View mContentView; + private final AutoclickLinearLayout mContentView; private final WindowManager mWindowManager; @@ -164,8 +171,9 @@ public class AutoclickTypePanel { R.drawable.accessibility_autoclick_resume); mContentView = - LayoutInflater.from(context) + (AutoclickLinearLayout) LayoutInflater.from(context) .inflate(R.layout.accessibility_autoclick_type_panel, null); + mContentView.setOnHoverChangedListener(mClickPanelController::onHoverChange); mLeftClickButton = mContentView.findViewById(R.id.accessibility_autoclick_left_click_layout); mRightClickButton = @@ -339,6 +347,10 @@ public class AutoclickTypePanel { return mPaused; } + public boolean isHovered() { + return mContentView.isHovered(); + } + /** Toggles the panel expanded or collapsed state. */ private void togglePanelExpansion(@AutoclickType int clickType) { final LinearLayout button = getButtonFromClickType(clickType); @@ -520,7 +532,7 @@ public class AutoclickTypePanel { @VisibleForTesting @NonNull - View getContentViewForTesting() { + AutoclickLinearLayout getContentViewForTesting() { return mContentView; } diff --git a/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java index 84402c85471f..12c35ae92cbe 100644 --- a/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java +++ b/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java @@ -177,6 +177,8 @@ abstract class DiscreteOpsRegistry { */ abstract void writeAndClearOldAccessHistory(); + void shutdown() {} + /** Remove all discrete op events. */ abstract void clearHistory(); diff --git a/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java index 604cb30294a9..dc11be9aadb6 100644 --- a/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java +++ b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java @@ -57,13 +57,18 @@ import java.util.Set; public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { private static final String TAG = "DiscreteOpsSqlRegistry"; + private static final long DB_WRITE_INTERVAL = Duration.ofMinutes(10).toMillis(); + private static final long EXPIRED_ENTRY_DELETION_INTERVAL = Duration.ofHours(6).toMillis(); + + // Event type handled by SqliteWriteHandler + private static final int WRITE_DATABASE_RECURRING = 1; + private static final int DELETE_EXPIRED_ENTRIES = 2; + private static final int WRITE_DATABASE_CACHE_FULL = 3; + private final Context mContext; private final DiscreteOpsDbHelper mDiscreteOpsDbHelper; private final SqliteWriteHandler mSqliteWriteHandler; private final DiscreteOpCache mDiscreteOpCache = new DiscreteOpCache(512); - private static final long THREE_HOURS = Duration.ofHours(3).toMillis(); - private static final int WRITE_CACHE_EVICTED_OP_EVENTS = 1; - private static final int DELETE_OLD_OP_EVENTS = 2; // Attribution chain id is used to identify an attribution source chain, This is // set for startOp only. PermissionManagerService resets this ID on device restart, so // we use previously persisted chain id as offset, and add it to chain id received from @@ -83,6 +88,9 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { mSqliteWriteHandler = new SqliteWriteHandler(thread.getLooper()); mDiscreteOpsDbHelper = new DiscreteOpsDbHelper(context, databaseFile); mChainIdOffset = mDiscreteOpsDbHelper.getLargestAttributionChainId(); + mSqliteWriteHandler.sendEmptyMessageDelayed(WRITE_DATABASE_RECURRING, DB_WRITE_INTERVAL); + mSqliteWriteHandler.sendEmptyMessageDelayed(DELETE_EXPIRED_ENTRIES, + EXPIRED_ENTRY_DELETION_INTERVAL); } @Override @@ -117,15 +125,14 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { } @Override - void writeAndClearOldAccessHistory() { - // Let the sql impl also follow the same disk write frequencies as xml, - // controlled by AppOpsService. + void shutdown() { + mSqliteWriteHandler.removeAllPendingMessages(); mDiscreteOpsDbHelper.insertDiscreteOps(mDiscreteOpCache.getAllEventsAndClear()); - if (!mSqliteWriteHandler.hasMessages(DELETE_OLD_OP_EVENTS)) { - if (mSqliteWriteHandler.sendEmptyMessageDelayed(DELETE_OLD_OP_EVENTS, THREE_HOURS)) { - Slog.w(TAG, "DELETE_OLD_OP_EVENTS is not queued"); - } - } + } + + @Override + void writeAndClearOldAccessHistory() { + // no-op } @Override @@ -175,7 +182,7 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { @Nullable String attributionTagFilter, int opFlagsFilter, Set<String> attributionExemptPkgs) { // flush the cache into database before read. - writeAndClearOldAccessHistory(); + mDiscreteOpsDbHelper.insertDiscreteOps(mDiscreteOpCache.getAllEventsAndClear()); boolean assembleChains = attributionExemptPkgs != null; IntArray opCodes = getAppOpCodes(filter, opNamesFilter); beginTimeMillis = Math.max(beginTimeMillis, Instant.now().minus(sDiscreteHistoryCutoff, @@ -363,20 +370,59 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { @Override public void handleMessage(Message msg) { switch (msg.what) { - case WRITE_CACHE_EVICTED_OP_EVENTS: - List<DiscreteOp> opEvents = (List<DiscreteOp>) msg.obj; - mDiscreteOpsDbHelper.insertDiscreteOps(opEvents); - break; - case DELETE_OLD_OP_EVENTS: + case WRITE_DATABASE_RECURRING -> { + try { + List<DiscreteOp> evictedEvents; + synchronized (mDiscreteOpCache) { + evictedEvents = mDiscreteOpCache.evict(); + } + mDiscreteOpsDbHelper.insertDiscreteOps(evictedEvents); + } finally { + mSqliteWriteHandler.sendEmptyMessageDelayed(WRITE_DATABASE_RECURRING, + DB_WRITE_INTERVAL); + // Schedule a cleanup to truncate older (before cutoff time) entries. + if (!mSqliteWriteHandler.hasMessages(DELETE_EXPIRED_ENTRIES)) { + mSqliteWriteHandler.sendEmptyMessageDelayed(DELETE_EXPIRED_ENTRIES, + EXPIRED_ENTRY_DELETION_INTERVAL); + } + } + } + case DELETE_EXPIRED_ENTRIES -> { long cutOffTimeStamp = System.currentTimeMillis() - sDiscreteHistoryCutoff; mDiscreteOpsDbHelper.execSQL( DiscreteOpsTable.DELETE_TABLE_DATA_BEFORE_ACCESS_TIME, new Object[]{cutOffTimeStamp}); - break; - default: - throw new IllegalStateException("Unexpected value: " + msg.what); + } + case WRITE_DATABASE_CACHE_FULL -> { + try { + List<DiscreteOp> evictedEvents; + synchronized (mDiscreteOpCache) { + evictedEvents = mDiscreteOpCache.evict(); + // if nothing to evict, just write the whole cache to database. + if (evictedEvents.isEmpty() + && mDiscreteOpCache.size() >= mDiscreteOpCache.capacity()) { + evictedEvents.addAll(mDiscreteOpCache.mCache); + mDiscreteOpCache.clear(); + } + } + mDiscreteOpsDbHelper.insertDiscreteOps(evictedEvents); + } finally { + // Just in case initial message is not scheduled. + if (!mSqliteWriteHandler.hasMessages(WRITE_DATABASE_RECURRING)) { + mSqliteWriteHandler.sendEmptyMessageDelayed(WRITE_DATABASE_RECURRING, + DB_WRITE_INTERVAL); + } + } + } + default -> throw new IllegalStateException("Unexpected value: " + msg.what); } } + + void removeAllPendingMessages() { + removeMessages(WRITE_DATABASE_RECURRING); + removeMessages(DELETE_EXPIRED_ENTRIES); + removeMessages(WRITE_DATABASE_CACHE_FULL); + } } /** @@ -390,6 +436,7 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { * 4) During shutdown. */ class DiscreteOpCache { + private static final String TAG = "DiscreteOpCache"; private final int mCapacity; private final ArraySet<DiscreteOp> mCache; @@ -404,23 +451,9 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { return; } mCache.add(opEvent); + if (mCache.size() >= mCapacity) { - if (DEBUG_LOG) { - Slog.i(TAG, "Current discrete ops cache size: " + mCache.size()); - } - List<DiscreteOp> evictedEvents = evict(); - if (DEBUG_LOG) { - Slog.i(TAG, "Evicted discrete ops size: " + evictedEvents.size()); - } - // if nothing to evict, just write the whole cache to disk - if (evictedEvents.isEmpty()) { - Slog.w(TAG, "No discrete ops event is evicted, write cache to db."); - evictedEvents.addAll(mCache); - mCache.clear(); - } - Message msg = mSqliteWriteHandler.obtainMessage( - WRITE_CACHE_EVICTED_OP_EVENTS, evictedEvents); - mSqliteWriteHandler.sendMessage(msg); + mSqliteWriteHandler.sendEmptyMessage(WRITE_DATABASE_CACHE_FULL); } } } @@ -461,6 +494,14 @@ public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { } } + int size() { + return mCache.size(); + } + + int capacity() { + return mCapacity; + } + /** * Remove all entries from the cache. */ diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java index 928a4b270b59..d267e0d9e536 100644 --- a/services/core/java/com/android/server/appop/HistoricalRegistry.java +++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java @@ -750,6 +750,7 @@ final class HistoricalRegistry { } // Do not call persistPendingHistory inside the memory lock, due to possible deadlock persistPendingHistory(); + mDiscreteRegistry.shutdown(); } void persistPendingHistory() { diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index f51e60c101e4..36686fc086f1 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -312,7 +312,6 @@ public class BackgroundActivityStartController { private final @ActivityManager.ProcessState int mCallingUidProcState; private final boolean mIsCallingUidPersistentSystemProcess; final BackgroundStartPrivileges mBalAllowedByPiSender; - final BackgroundStartPrivileges mBalAllowedByPiCreatorWithHardening; final BackgroundStartPrivileges mBalAllowedByPiCreator; private final String mRealCallingPackage; private final int mRealCallingUid; @@ -379,22 +378,14 @@ public class BackgroundActivityStartController { if (mAutoOptInCaller) { // grant BAL privileges unless explicitly opted out - mBalAllowedByPiCreatorWithHardening = mBalAllowedByPiCreator = + mBalAllowedByPiCreator = callerBackgroundActivityStartMode == MODE_BACKGROUND_ACTIVITY_START_DENIED ? BackgroundStartPrivileges.NONE : BackgroundStartPrivileges.ALLOW_BAL; } else { // for PendingIntents we restrict BAL based on target_sdk - mBalAllowedByPiCreatorWithHardening = getBackgroundStartPrivilegesAllowedByCreator( + mBalAllowedByPiCreator = getBackgroundStartPrivilegesAllowedByCreator( callingUid, callingPackage, checkedOptions); - final BackgroundStartPrivileges mBalAllowedByPiCreatorWithoutHardening = - callerBackgroundActivityStartMode - == MODE_BACKGROUND_ACTIVITY_START_DENIED - ? BackgroundStartPrivileges.NONE - : BackgroundStartPrivileges.ALLOW_BAL; - mBalAllowedByPiCreator = balRequireOptInByPendingIntentCreator() - ? mBalAllowedByPiCreatorWithHardening - : mBalAllowedByPiCreatorWithoutHardening; } if (mAutoOptInReason != null) { @@ -585,9 +576,8 @@ public class BackgroundActivityStartController { if (mCallerApp != null) { sb.append("; inVisibleTask: ").append(mCallerApp.hasActivityInVisibleTask()); } - sb.append("; balAllowedByPiCreator: ").append(mBalAllowedByPiCreator); - sb.append("; balAllowedByPiCreatorWithHardening: ") - .append(mBalAllowedByPiCreatorWithHardening); + sb.append("; balAllowedByPiCreator: ") + .append(mBalAllowedByPiCreator); if (mResultForCaller != null) { sb.append("; resultIfPiCreatorAllowsBal: ") .append(balCodeToString(mResultForCaller.mCode)); @@ -638,14 +628,13 @@ public class BackgroundActivityStartController { } static class BalVerdict { - static final BalVerdict BLOCK = new BalVerdict(BAL_BLOCK, false, "Blocked"); + static final BalVerdict BLOCK = new BalVerdict(BAL_BLOCK, "Blocked"); static final BalVerdict ALLOW_BY_DEFAULT = - new BalVerdict(BAL_ALLOW_DEFAULT, false, "Default"); + new BalVerdict(BAL_ALLOW_DEFAULT, "Default"); // Careful using this - it will bypass all ASM checks. static final BalVerdict ALLOW_PRIVILEGED = - new BalVerdict(BAL_ALLOW_ALLOWLISTED_UID, false, "PRIVILEGED"); + new BalVerdict(BAL_ALLOW_ALLOWLISTED_UID, "PRIVILEGED"); private final @BalCode int mCode; - private final boolean mBackground; private final String mMessage; private String mProcessInfo; // indicates BAL would be blocked because only creator of the PI has the privilege to allow @@ -654,8 +643,7 @@ public class BackgroundActivityStartController { /** indicates that this verdict is based on the real calling UID and not the calling UID */ private boolean mBasedOnRealCaller; - BalVerdict(@BalCode int balCode, boolean background, String message) { - this.mBackground = background; + BalVerdict(@BalCode int balCode, String message) { this.mCode = balCode; this.mMessage = message; } @@ -708,16 +696,7 @@ public class BackgroundActivityStartController { builder.append(" [realCaller]"); } if (DEBUG_ACTIVITY_STARTS) { - builder.append(" ("); - if (mBackground) { - builder.append("Background "); - } - builder.append("Activity start "); - if (mCode == BAL_BLOCK) { - builder.append("denied"); - } else { - builder.append("allowed: ").append(mMessage); - } + builder.append(" (").append(mMessage); if (mProcessInfo != null) { builder.append(" "); builder.append(mProcessInfo); @@ -795,7 +774,6 @@ public class BackgroundActivityStartController { // to realCallingUid when calculating resultForRealCaller below. if (getService().hasActiveVisibleWindow(realCallingSdkSandboxUidToAppUid)) { state.setResultForRealCaller(new BalVerdict(BAL_ALLOW_SDK_SANDBOX, - /*background*/ false, "uid in SDK sandbox has visible (non-toast) window")); return allowBasedOnRealCaller(state); } @@ -1059,8 +1037,7 @@ public class BackgroundActivityStartController { || state.mAppSwitchState == APP_SWITCH_FG_ONLY || isHomeApp(state.mCallingUid, state.mCallingPackage); if (appSwitchAllowedOrFg && state.mCallingUidHasVisibleActivity) { - return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, - /*background*/ false, "callingUid has visible window"); + return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "callingUid has visible window"); } return BalVerdict.BLOCK; }; @@ -1068,7 +1045,7 @@ public class BackgroundActivityStartController { private final BalExemptionCheck mCheckCallerNonAppVisible = state -> { if (state.mCallingUidHasNonAppVisibleWindow) { return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW, - /*background*/ false, "callingUid has non-app visible window " + "callingUid has non-app visible window " + getService().mActiveUids.getNonAppVisibleWindowDetails(state.mCallingUid)); } return BalVerdict.BLOCK; @@ -1080,9 +1057,7 @@ public class BackgroundActivityStartController { if (state.mCallingUid == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID || callingAppId == Process.NFC_UID) { - return new BalVerdict( - BAL_ALLOW_ALLOWLISTED_UID, /*background*/ false, - "Important callingUid"); + return new BalVerdict(BAL_ALLOW_ALLOWLISTED_UID, "Important callingUid"); } return BalVerdict.BLOCK; }; @@ -1090,9 +1065,7 @@ public class BackgroundActivityStartController { private final BalExemptionCheck mCheckCallerIsAllowlistedComponent = state -> { // Always allow home application to start activities. if (isHomeApp(state.mCallingUid, state.mCallingPackage)) { - return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, - /*background*/ false, - "Home app"); + return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Home app"); } final int callingAppId = UserHandle.getAppId(state.mCallingUid); @@ -1100,37 +1073,31 @@ public class BackgroundActivityStartController { final WindowState imeWindow = getService().mRootWindowContainer.getCurrentInputMethodWindow(); if (imeWindow != null && callingAppId == imeWindow.mOwnerUid) { - return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, - /*background*/ false, - "Active ime"); + return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Active ime"); } // don't abort if the callingUid is a persistent system process if (state.mIsCallingUidPersistentSystemProcess) { return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, - /*background*/ false, "callingUid is persistent system process"); + "callingUid is persistent system process"); } // don't abort if the caller has the same uid as the recents component if (getSupervisor().mRecentTasks.isCallerRecents(state.mCallingUid)) { - return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, - /*background*/ true, "Recents Component"); + return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Recents Component"); } // don't abort if the callingUid is the device owner if (getService().isDeviceOwner(state.mCallingUid)) { - return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, - /*background*/ true, "Device Owner"); + return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Device Owner"); } // don't abort if the callingUid is a affiliated profile owner if (getService().isAffiliatedProfileOwner(state.mCallingUid)) { - return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, - /*background*/ true, "Affiliated Profile Owner"); + return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Affiliated Profile Owner"); } // don't abort if the callingUid has companion device final int callingUserId = UserHandle.getUserId(state.mCallingUid); if (getService().isAssociatedCompanionApp(callingUserId, state.mCallingUid)) { - return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, - /*background*/ true, "Companion App"); + return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, "Companion App"); } return BalVerdict.BLOCK; }; @@ -1139,7 +1106,6 @@ public class BackgroundActivityStartController { // don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission if (hasBalPermission(state.mCallingUid, state.mCallingPid)) { return new BalVerdict(BAL_ALLOW_PERMISSION, - /*background*/ true, "START_ACTIVITIES_FROM_BACKGROUND permission granted"); } return BalVerdict.BLOCK; @@ -1149,7 +1115,7 @@ public class BackgroundActivityStartController { if (getService().hasSystemAlertWindowPermission(state.mCallingUid, state.mCallingPid, state.mCallingPackage)) { return new BalVerdict(BAL_ALLOW_SAW_PERMISSION, - /*background*/ true, "SYSTEM_ALERT_WINDOW permission is granted"); + "SYSTEM_ALERT_WINDOW permission is granted"); } return BalVerdict.BLOCK; }; @@ -1159,7 +1125,7 @@ public class BackgroundActivityStartController { if (isSystemExemptFlagEnabled() && getService().getAppOpsManager().checkOpNoThrow( AppOpsManager.OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION, state.mCallingUid, state.mCallingPackage) == AppOpsManager.MODE_ALLOWED) { - return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ true, + return new BalVerdict(BAL_ALLOW_PERMISSION, "OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION appop is granted"); } return BalVerdict.BLOCK; @@ -1200,8 +1166,7 @@ public class BackgroundActivityStartController { || state.mAppSwitchState == APP_SWITCH_FG_ONLY || isHomeApp(state.mRealCallingUid, state.mRealCallingPackage); if (appSwitchAllowedOrFg && state.mRealCallingUidHasVisibleActivity) { - return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, - /*background*/ false, "realCallingUid has visible window"); + return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "realCallingUid has visible window"); } return BalVerdict.BLOCK; }; @@ -1209,9 +1174,9 @@ public class BackgroundActivityStartController { private final BalExemptionCheck mCheckRealCallerNonAppVisible = state -> { if (state.mRealCallingUidHasNonAppVisibleWindow) { return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW, - /*background*/ false, "realCallingUid has non-app visible window " - + getService().mActiveUids.getNonAppVisibleWindowDetails( - state.mRealCallingUid)); + "realCallingUid has non-app visible window " + + getService().mActiveUids.getNonAppVisibleWindowDetails( + state.mRealCallingUid)); } return BalVerdict.BLOCK; }; @@ -1230,9 +1195,7 @@ public class BackgroundActivityStartController { == MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS; if (allowAlways && hasBalPermission(state.mRealCallingUid, state.mRealCallingPid)) { - return new BalVerdict(BAL_ALLOW_PERMISSION, - /*background*/ false, - "realCallingUid has BAL permission."); + return new BalVerdict(BAL_ALLOW_PERMISSION, "realCallingUid has BAL permission."); } return BalVerdict.BLOCK; }; @@ -1245,7 +1208,7 @@ public class BackgroundActivityStartController { && getService().hasSystemAlertWindowPermission(state.mRealCallingUid, state.mRealCallingPid, state.mRealCallingPackage)) { return new BalVerdict(BAL_ALLOW_SAW_PERMISSION, - /*background*/ true, "SYSTEM_ALERT_WINDOW permission is granted"); + "SYSTEM_ALERT_WINDOW permission is granted"); } return BalVerdict.BLOCK; }; @@ -1258,7 +1221,6 @@ public class BackgroundActivityStartController { if ((allowAlways || state.mAllowBalExemptionForSystemProcess) && state.mIsRealCallingUidPersistentSystemProcess) { return new BalVerdict(BAL_ALLOW_ALLOWLISTED_UID, - /*background*/ false, "realCallingUid is persistent system process AND intent " + "sender forced to allow."); } @@ -1270,7 +1232,6 @@ public class BackgroundActivityStartController { if (getService().isAssociatedCompanionApp( UserHandle.getUserId(state.mRealCallingUid), state.mRealCallingUid)) { return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, - /*background*/ false, "realCallingUid is a companion app."); } return BalVerdict.BLOCK; diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java index ccf1aedb3177..31b239421baf 100644 --- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java +++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java @@ -125,27 +125,27 @@ class BackgroundLaunchProcessController { long lastActivityFinishTime) { // Allow if the proc is instrumenting with background activity starts privs. if (checkConfiguration.checkOtherExemptions && hasBackgroundActivityStartPrivileges) { - return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ true, + return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ "process instrumenting with background activity starts privileges"); } // Allow if the flag was explicitly set. if (checkConfiguration.checkOtherExemptions && isBackgroundStartAllowedByToken(uid, packageName, checkConfiguration.isCheckingForFgsStart)) { return new BalVerdict(balImprovedMetrics() ? BAL_ALLOW_TOKEN : BAL_ALLOW_PERMISSION, - /*background*/ true, "process allowed by token"); + /*background*/ "process allowed by token"); } // Allow if the caller is bound by a UID that's currently foreground. // But still respect the appSwitchState. if (checkConfiguration.checkVisibility && appSwitchState != APP_SWITCH_DISALLOW && isBoundByForegroundUid()) { return new BalVerdict(balImprovedMetrics() ? BAL_ALLOW_BOUND_BY_FOREGROUND - : BAL_ALLOW_VISIBLE_WINDOW, /*background*/ false, + : BAL_ALLOW_VISIBLE_WINDOW, /*background*/ "process bound by foreground uid"); } // Allow if the caller has an activity in any foreground task. if (checkConfiguration.checkOtherExemptions && hasActivityInVisibleTask && appSwitchState != APP_SWITCH_DISALLOW) { - return new BalVerdict(BAL_ALLOW_FOREGROUND, /*background*/ false, + return new BalVerdict(BAL_ALLOW_FOREGROUND, /*background*/ "process has activity in foreground task"); } @@ -160,7 +160,7 @@ class BackgroundLaunchProcessController { long timeSinceLastStartOrFinish = now - Math.max(lastActivityLaunchTime, lastActivityFinishTime); if (timeSinceLastStartOrFinish < checkConfiguration.gracePeriod) { - return new BalVerdict(BAL_ALLOW_GRACE_PERIOD, /*background*/ true, + return new BalVerdict(BAL_ALLOW_GRACE_PERIOD, /*background*/ "within " + checkConfiguration.gracePeriod + "ms grace period (" + timeSinceLastStartOrFinish + "ms)"); } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 64c19ff70c9f..574ab05075c4 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -6575,6 +6575,22 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp .getKeyguardController().isKeyguardLocked(mDisplayId); } + boolean isKeyguardLockedOrAodShowing() { + return isKeyguardLocked() || isAodShowing(); + } + + /** + * @return whether aod is showing for this display + */ + boolean isAodShowing() { + final boolean isAodShowing = mRootWindowContainer.mTaskSupervisor + .getKeyguardController().isAodShowing(mDisplayId); + if (mDisplayId == DEFAULT_DISPLAY && isAodShowing) { + return !isKeyguardGoingAway(); + } + return isAodShowing; + } + /** * @return whether keyguard is going away on this display */ diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index 6d73739e5046..4eeed5ec8423 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION; @@ -216,6 +217,9 @@ class KeyguardController { } else if (keyguardShowing && !state.mKeyguardShowing) { transition.addFlag(TRANSIT_FLAG_KEYGUARD_APPEARING); } + if (mWindowManager.mFlags.mAodTransition && aodShowing && !state.mAodShowing) { + transition.addFlag(TRANSIT_FLAG_AOD_APPEARING); + } } } // Update the task snapshot if the screen will not be turned off. To make sure that the @@ -238,19 +242,28 @@ class KeyguardController { state.mAodShowing = aodShowing; state.writeEventLog("setKeyguardShown"); - if (keyguardChanged) { - // Irrelevant to AOD. - state.mKeyguardGoingAway = false; - if (keyguardShowing) { - state.mDismissalRequested = false; + if (keyguardChanged || (mWindowManager.mFlags.mAodTransition && aodChanged)) { + if (keyguardChanged) { + // Irrelevant to AOD. + state.mKeyguardGoingAway = false; + if (keyguardShowing) { + state.mDismissalRequested = false; + } } if (goingAwayRemoved - || (keyguardShowing && !Display.isOffState(dc.getDisplayInfo().state))) { + || (keyguardShowing && !Display.isOffState(dc.getDisplayInfo().state)) + || (mWindowManager.mFlags.mAodTransition && aodShowing)) { // Keyguard decided to show or stopped going away. Send a transition to animate back // to the locked state before holding the sleep token again if (!ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) { - dc.requestTransitionAndLegacyPrepare( - TRANSIT_TO_FRONT, TRANSIT_FLAG_KEYGUARD_APPEARING, /* trigger= */ null); + if (keyguardChanged) { + dc.requestTransitionAndLegacyPrepare(TRANSIT_TO_FRONT, + TRANSIT_FLAG_KEYGUARD_APPEARING, /* trigger= */ null); + } + if (mWindowManager.mFlags.mAodTransition && aodChanged && aodShowing) { + dc.requestTransitionAndLegacyPrepare(TRANSIT_TO_FRONT, + TRANSIT_FLAG_AOD_APPEARING, /* trigger= */ null); + } } dc.mWallpaperController.adjustWallpaperWindows(); dc.executeAppTransition(); diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 3db1d50f3d6a..c78cdaa10df2 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -36,6 +36,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_FLAG_AOD_APPEARING; import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; import static android.view.WindowManager.TRANSIT_OPEN; @@ -980,6 +981,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { return false; } + boolean isInAodAppearTransition() { + return (mFlags & TRANSIT_FLAG_AOD_APPEARING) != 0; + } + /** * Specifies configuration change explicitly for the window container, so it can be chosen as * transition target. This is usually used with transition mode diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 11c5c9345ab2..9b3b4451a746 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -526,6 +526,19 @@ class TransitionController { return false; } + boolean isInAodAppearTransition() { + if (mCollectingTransition != null && mCollectingTransition.isInAodAppearTransition()) { + return true; + } + for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) { + if (mWaitingTransitions.get(i).isInAodAppearTransition()) return true; + } + for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { + if (mPlayingTransitions.get(i).isInAodAppearTransition()) return true; + } + return false; + } + /** * @return A pair of the transition and restore-behind target for the given {@param container}. * @param container An ancestor of a transient-launch activity diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index a8b9fedcdc73..644417ec98e5 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -166,6 +166,14 @@ class WallpaperController { mFindResults.setWallpaperTarget(w); return false; } + } else if (mService.mFlags.mAodTransition + && mDisplayContent.isKeyguardLockedOrAodShowing()) { + if (mService.mPolicy.isKeyguardHostWindow(w.mAttrs) + && w.mTransitionController.isInAodAppearTransition()) { + if (DEBUG_WALLPAPER) Slog.v(TAG, "Found aod transition wallpaper target: " + w); + mFindResults.setWallpaperTarget(w); + return true; + } } final boolean animationWallpaper = animatingContainer != null @@ -678,7 +686,8 @@ class WallpaperController { private WallpaperWindowToken getTokenForTarget(WindowState target) { if (target == null) return null; WindowState window = mFindResults.getTopWallpaper( - target.canShowWhenLocked() && mService.isKeyguardLocked()); + (target.canShowWhenLocked() && mService.isKeyguardLocked()) + || (mService.mFlags.mAodTransition && mDisplayContent.isAodShowing())); return window == null ? null : window.mToken.asWallpaperToken(); } @@ -721,7 +730,9 @@ class WallpaperController { if (mFindResults.wallpaperTarget == null && mFindResults.useTopWallpaperAsTarget) { mFindResults.setWallpaperTarget( - mFindResults.getTopWallpaper(mDisplayContent.isKeyguardLocked())); + mFindResults.getTopWallpaper(mService.mFlags.mAodTransition + ? mDisplayContent.isKeyguardLockedOrAodShowing() + : mDisplayContent.isKeyguardLocked())); } } @@ -885,11 +896,17 @@ class WallpaperController { if (mDisplayContent.mWmService.mFlags.mEnsureWallpaperInTransitions) { visibleRequested = mWallpaperTarget != null && mWallpaperTarget.isVisibleRequested(); } - updateWallpaperTokens(visibleRequested, mDisplayContent.isKeyguardLocked()); + updateWallpaperTokens(visibleRequested, + mService.mFlags.mAodTransition + ? mDisplayContent.isKeyguardLockedOrAodShowing() + : mDisplayContent.isKeyguardLocked()); ProtoLog.v(WM_DEBUG_WALLPAPER, "Wallpaper at display %d - visibility: %b, keyguardLocked: %b", - mDisplayContent.getDisplayId(), visible, mDisplayContent.isKeyguardLocked()); + mDisplayContent.getDisplayId(), visible, + mService.mFlags.mAodTransition + ? mDisplayContent.isKeyguardLockedOrAodShowing() + : mDisplayContent.isKeyguardLocked()); if (visible && mLastFrozen != mFindResults.isWallpaperTargetForLetterbox) { mLastFrozen = mFindResults.isWallpaperTargetForLetterbox; diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceMockedTest.java index 6ad3df1dd6f2..ac11216bdf0a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceMockedTest.java @@ -102,11 +102,12 @@ import java.io.FileInputStream; import java.io.IOException; /** - * Run as {@code atest FrameworksMockingServicesTests:com.android.server.pm.UserManagerServiceTest} + * Run as {@code atest + * FrameworksMockingServicesTests:com.android.server.pm.UserManagerServiceMockedTest} */ -public final class UserManagerServiceTest { +public final class UserManagerServiceMockedTest { - private static final String TAG = UserManagerServiceTest.class.getSimpleName(); + private static final String TAG = UserManagerServiceMockedTest.class.getSimpleName(); /** * Id for a simple user (that doesn't have profiles). diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java index 17d8882b487c..ea83825cd810 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java @@ -571,6 +571,95 @@ public class AutoclickControllerTest { assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isNotEqualTo(-1); } + @Test + @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void pauseButton_panelNotHovered_clickNotTriggeredWhenPaused() { + injectFakeMouseActionHoverMoveEvent(); + + // Pause autoclick and ensure the panel is not hovered. + AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class); + when(mockAutoclickTypePanel.isPaused()).thenReturn(true); + when(mockAutoclickTypePanel.isHovered()).thenReturn(false); + mController.mAutoclickTypePanel = mockAutoclickTypePanel; + + // Send hover move event. + MotionEvent hoverMove = MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 100, + /* action= */ MotionEvent.ACTION_HOVER_MOVE, + /* x= */ 30f, + /* y= */ 0f, + /* metaState= */ 0); + hoverMove.setSource(InputDevice.SOURCE_MOUSE); + mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0); + + // Verify click is not triggered. + assertThat(mController.mClickScheduler.getIsActiveForTesting()).isFalse(); + assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isEqualTo(-1); + } + + @Test + @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void pauseButton_panelHovered_clickTriggeredWhenPaused() { + injectFakeMouseActionHoverMoveEvent(); + + // Pause autoclick and hover the panel. + AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class); + when(mockAutoclickTypePanel.isPaused()).thenReturn(true); + when(mockAutoclickTypePanel.isHovered()).thenReturn(true); + mController.mAutoclickTypePanel = mockAutoclickTypePanel; + + // Send hover move event. + MotionEvent hoverMove = MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 100, + /* action= */ MotionEvent.ACTION_HOVER_MOVE, + /* x= */ 30f, + /* y= */ 0f, + /* metaState= */ 0); + hoverMove.setSource(InputDevice.SOURCE_MOUSE); + mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0); + + // Verify click is triggered. + assertThat(mController.mClickScheduler.getIsActiveForTesting()).isTrue(); + assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isNotEqualTo(-1); + } + + @Test + @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void pauseButton_unhoveringCancelsClickWhenPaused() { + injectFakeMouseActionHoverMoveEvent(); + + // Pause autoclick and hover the panel. + AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class); + when(mockAutoclickTypePanel.isPaused()).thenReturn(true); + when(mockAutoclickTypePanel.isHovered()).thenReturn(true); + mController.mAutoclickTypePanel = mockAutoclickTypePanel; + + // Send hover move event. + MotionEvent hoverMove = MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 100, + /* action= */ MotionEvent.ACTION_HOVER_MOVE, + /* x= */ 30f, + /* y= */ 0f, + /* metaState= */ 0); + hoverMove.setSource(InputDevice.SOURCE_MOUSE); + mController.onMotionEvent(hoverMove, hoverMove, /* policyFlags= */ 0); + + // Verify click is triggered. + assertThat(mController.mClickScheduler.getIsActiveForTesting()).isTrue(); + assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isNotEqualTo(-1); + + // Now simulate the pointer being moved outside the panel. + when(mockAutoclickTypePanel.isHovered()).thenReturn(false); + mController.clickPanelController.onHoverChange(/* hovered= */ false); + + // Verify pending click is canceled. + assertThat(mController.mClickScheduler.getIsActiveForTesting()).isFalse(); + assertThat(mController.mClickScheduler.getScheduledClickTimeForTesting()).isEqualTo(-1); + } + private void injectFakeMouseActionHoverMoveEvent() { MotionEvent event = getFakeMotionHoverMoveEvent(); event.setSource(InputDevice.SOURCE_MOUSE); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickLinearLayoutTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickLinearLayoutTest.java new file mode 100644 index 000000000000..9e629f7c87a2 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickLinearLayoutTest.java @@ -0,0 +1,102 @@ +/* + * Copyright 2025 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.accessibility.autoclick; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static com.google.common.truth.Truth.assertThat; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableContext; +import android.view.MotionEvent; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Test cases for {@link AutoclickLinearLayout}. */ +@RunWith(AndroidTestingRunner.class) +public class AutoclickLinearLayoutTest { + private boolean mHovered; + + private final AutoclickLinearLayout.OnHoverChangedListener mListener = + new AutoclickLinearLayout.OnHoverChangedListener() { + @Override + public void onHoverChanged(boolean hovered) { + mHovered = hovered; + } + }; + + @Rule + public TestableContext mTestableContext = + new TestableContext(getInstrumentation().getContext()); + private AutoclickLinearLayout mAutoclickLinearLayout; + + @Before + public void setUp() { + mAutoclickLinearLayout = new AutoclickLinearLayout(mTestableContext); + } + + @Test + public void autoclickLinearLayout_hoverChangedListener_setHovered() { + mHovered = false; + mAutoclickLinearLayout.setOnHoverChangedListener(mListener); + mAutoclickLinearLayout.onHoverChanged(/* hovered= */ true); + assertThat(mHovered).isTrue(); + } + + @Test + public void autoclickLinearLayout_hoverChangedListener_setNotHovered() { + mHovered = true; + + mAutoclickLinearLayout.setOnHoverChangedListener(mListener); + mAutoclickLinearLayout.onHoverChanged(/* hovered= */ false); + assertThat(mHovered).isFalse(); + } + + @Test + public void autoclickLinearLayout_onInterceptHoverEvent_hovered() { + mAutoclickLinearLayout.setHovered(false); + mAutoclickLinearLayout.onInterceptHoverEvent( + getFakeMotionEvent(MotionEvent.ACTION_HOVER_ENTER)); + assertThat(mAutoclickLinearLayout.isHovered()).isTrue(); + + mAutoclickLinearLayout.setHovered(false); + mAutoclickLinearLayout.onInterceptHoverEvent( + getFakeMotionEvent(MotionEvent.ACTION_HOVER_MOVE)); + assertThat(mAutoclickLinearLayout.isHovered()).isTrue(); + } + + @Test + public void autoclickLinearLayout_onInterceptHoverEvent_hoveredExit() { + mAutoclickLinearLayout.setHovered(true); + mAutoclickLinearLayout.onInterceptHoverEvent( + getFakeMotionEvent(MotionEvent.ACTION_HOVER_EXIT)); + assertThat(mAutoclickLinearLayout.isHovered()).isFalse(); + } + + private MotionEvent getFakeMotionEvent(int motionEventAction) { + return MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 0, + /* action= */ motionEventAction, + /* x= */ 0, + /* y= */ 0, + /* metaState= */ 0); + } +} diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java index 9e123406dff5..f7b16c808c50 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickTypePanelTest.java @@ -78,6 +78,7 @@ public class AutoclickTypePanelTest { private @AutoclickType int mActiveClickType = AUTOCLICK_TYPE_LEFT_CLICK; private boolean mPaused; + private boolean mHovered; private final ClickPanelControllerInterface clickPanelController = new ClickPanelControllerInterface() { @@ -90,6 +91,11 @@ public class AutoclickTypePanelTest { public void toggleAutoclickPause(boolean paused) { mPaused = paused; } + + @Override + public void onHoverChange(boolean hovered) { + mHovered = hovered; + } }; @Before @@ -412,6 +418,33 @@ public class AutoclickTypePanelTest { upEvent.recycle(); } + @Test + public void hovered_IsHovered() { + AutoclickLinearLayout mContext = mAutoclickTypePanel.getContentViewForTesting(); + + assertThat(mAutoclickTypePanel.isHovered()).isFalse(); + mContext.onInterceptHoverEvent(getFakeMotionHoverMoveEvent()); + assertThat(mAutoclickTypePanel.isHovered()).isTrue(); + } + + @Test + public void hovered_OnHoverChange_isHovered() { + AutoclickLinearLayout mContext = mAutoclickTypePanel.getContentViewForTesting(); + + mHovered = false; + mContext.onHoverChanged(true); + assertThat(mHovered).isTrue(); + } + + @Test + public void hovered_OnHoverChange_isNotHovered() { + AutoclickLinearLayout mContext = mAutoclickTypePanel.getContentViewForTesting(); + + mHovered = true; + mContext.onHoverChanged(false); + assertThat(mHovered).isFalse(); + } + private void verifyButtonHasSelectedStyle(@NonNull LinearLayout button) { GradientDrawable gradientDrawable = (GradientDrawable) button.getBackground(); assertThat(gradientDrawable.getColor().getDefaultColor()) @@ -426,4 +459,14 @@ public class AutoclickTypePanelTest { assertThat(params.x).isEqualTo(expectedPosition[2]); assertThat(params.y).isEqualTo(expectedPosition[3]); } + + private MotionEvent getFakeMotionHoverMoveEvent() { + return MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 0, + /* action= */ MotionEvent.ACTION_HOVER_MOVE, + /* x= */ 0, + /* y= */ 0, + /* metaState= */ 0); + } } diff --git a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java index 84713079c9d3..01fee7f66497 100644 --- a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java +++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java @@ -226,9 +226,9 @@ public class DiscreteAppOpSqlPersistenceTest { mDiscreteRegistry.recordDiscreteAccess(event2); } - /** This clears in-memory cache and push records into the database. */ private void flushDiscreteOpsToDatabase() { - mDiscreteRegistry.writeAndClearOldAccessHistory(); + // This clears in-memory cache and push records from cache into the database. + mDiscreteRegistry.shutdown(); } /** diff --git a/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java index 21cc3bac3938..8eea1c73d4f2 100644 --- a/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java +++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java @@ -106,7 +106,8 @@ public class DiscreteOpsMigrationAndRollbackTest { opEvent.getDuration(), opEvent.getAttributionFlags(), (int) opEvent.getChainId(), DiscreteOpsRegistry.ACCESS_TYPE_NOTE_OP); } - sqlRegistry.writeAndClearOldAccessHistory(); + // flush records from cache to the database. + sqlRegistry.shutdown(); assertThat(sqlRegistry.getAllDiscreteOps().size()).isEqualTo(RECORD_COUNT); assertThat(sqlRegistry.getLargestAttributionChainId()).isEqualTo(RECORD_COUNT); diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java index c934c55dfb8e..e3a8776aca6a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java @@ -486,7 +486,7 @@ public class BackgroundActivityStartControllerExemptionTests { // setup state when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt(), any())).thenReturn( - new BalVerdict(BAL_ALLOW_FOREGROUND, false, "allowed")); + new BalVerdict(BAL_ALLOW_FOREGROUND, "allowed")); when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW); // prepare call @@ -523,7 +523,7 @@ public class BackgroundActivityStartControllerExemptionTests { mCallerApp); when(mService.getBalAppSwitchesState()).thenReturn(APP_SWITCH_ALLOW); when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt(), any())).thenReturn( - new BalVerdict(BAL_ALLOW_FOREGROUND, false, "allowed")); + new BalVerdict(BAL_ALLOW_FOREGROUND, "allowed")); // prepare call PendingIntentRecord originatingPendingIntent = mPendingIntentRecord; @@ -572,7 +572,7 @@ public class BackgroundActivityStartControllerExemptionTests { when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt(), any())).thenReturn( BalVerdict.BLOCK); when(otherProcess.areBackgroundActivityStartsAllowed(anyInt(), any())).thenReturn( - new BalVerdict(BAL_ALLOW_FOREGROUND, false, "allowed")); + new BalVerdict(BAL_ALLOW_FOREGROUND, "allowed")); // prepare call PendingIntentRecord originatingPendingIntent = mPendingIntentRecord; diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerLogTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerLogTests.java index 99e730ae76cf..cd5f3912bfc6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerLogTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerLogTests.java @@ -92,7 +92,7 @@ public class BackgroundActivityStartControllerLogTests { @Test public void intent_visible_noLog() { useIntent(); - BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "visible"); + BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "visible"); mState.setResultForCaller(finalVerdict); mState.setResultForRealCaller(BalVerdict.BLOCK); assertThat(mController.shouldLogStats(finalVerdict, mState)).isFalse(); @@ -101,7 +101,7 @@ public class BackgroundActivityStartControllerLogTests { @Test public void intent_saw_log() { useIntent(); - BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false, "SAW"); + BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, "SAW"); mState.setResultForCaller(finalVerdict); mState.setResultForRealCaller(BalVerdict.BLOCK); assertThat(mController.shouldLogStats(finalVerdict, mState)).isTrue(); @@ -111,7 +111,7 @@ public class BackgroundActivityStartControllerLogTests { @Test public void pendingIntent_callerOnly_saw_log() { usePendingIntent(); - BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false, "SAW"); + BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, "SAW"); mState.setResultForCaller(finalVerdict); mState.setResultForRealCaller(BalVerdict.BLOCK); assertThat(mController.shouldLogStats(finalVerdict, mState)).isTrue(); @@ -121,7 +121,7 @@ public class BackgroundActivityStartControllerLogTests { @Test public void pendingIntent_realCallerOnly_saw_log() { usePendingIntent(); - BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false, "SAW") + BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, "SAW") .setBasedOnRealCaller(); mState.setResultForCaller(BalVerdict.BLOCK); mState.setResultForRealCaller(finalVerdict); @@ -131,7 +131,7 @@ public class BackgroundActivityStartControllerLogTests { @Test public void intent_shouldLogIntentActivity() { - BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false, "SAW"); + BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, "SAW"); useIntent(APP1_UID); assertThat(mController.shouldLogIntentActivity(finalVerdict, mState)).isFalse(); useIntent(SYSTEM_UID); @@ -140,7 +140,7 @@ public class BackgroundActivityStartControllerLogTests { @Test public void pendingIntent_shouldLogIntentActivityForCaller() { - BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false, "SAW"); + BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, "SAW"); usePendingIntent(APP1_UID, APP2_UID); assertThat(mController.shouldLogIntentActivity(finalVerdict, mState)).isFalse(); usePendingIntent(SYSTEM_UID, SYSTEM_UID); @@ -153,7 +153,7 @@ public class BackgroundActivityStartControllerLogTests { @Test public void pendingIntent_shouldLogIntentActivityForRealCaller() { - BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, false, + BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_SAW_PERMISSION, "SAW").setBasedOnRealCaller(); usePendingIntent(APP1_UID, APP2_UID); assertThat(mController.shouldLogIntentActivity(finalVerdict, mState)).isFalse(); @@ -168,7 +168,7 @@ public class BackgroundActivityStartControllerLogTests { @Test public void pendingIntent_realCallerOnly_visible_noLog() { usePendingIntent(); - BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, + BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "visible").setBasedOnRealCaller(); mState.setResultForCaller(BalVerdict.BLOCK); mState.setResultForRealCaller(finalVerdict); @@ -178,7 +178,7 @@ public class BackgroundActivityStartControllerLogTests { @Test public void pendingIntent_callerOnly_visible_noLog() { usePendingIntent(); - BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "visible"); + BalVerdict finalVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "visible"); mState.setResultForCaller(finalVerdict); mState.setResultForRealCaller(BalVerdict.BLOCK); assertThat(mController.shouldLogStats(finalVerdict, mState)).isTrue(); diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java index 51706d72cb35..fe9a6e746513 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java @@ -305,7 +305,7 @@ public class BackgroundActivityStartControllerTests { @Test public void testRegularActivityStart_allowedByCaller_isAllowed() { // setup state - BalVerdict callerVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, + BalVerdict callerVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "CallerIsVisible"); mController.setCallerVerdict(callerVerdict); mController.setRealCallerVerdict(BalVerdict.BLOCK); @@ -340,7 +340,7 @@ public class BackgroundActivityStartControllerTests { @Test public void testRegularActivityStart_allowedByRealCaller_isAllowed() { // setup state - BalVerdict realCallerVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, + BalVerdict realCallerVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "RealCallerIsVisible"); mController.setCallerVerdict(BalVerdict.BLOCK); mController.setRealCallerVerdict(realCallerVerdict); @@ -373,9 +373,9 @@ public class BackgroundActivityStartControllerTests { public void testRegularActivityStart_allowedByCallerAndRealCaller_returnsCallerVerdict() { // setup state BalVerdict callerVerdict = - new BalVerdict(BAL_ALLOW_PERMISSION, false, "CallerHasPermission"); + new BalVerdict(BAL_ALLOW_PERMISSION, "CallerHasPermission"); BalVerdict realCallerVerdict = - new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "RealCallerIsVisible"); + new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "RealCallerIsVisible"); mController.setCallerVerdict(callerVerdict); mController.setRealCallerVerdict(realCallerVerdict); @@ -411,9 +411,9 @@ public class BackgroundActivityStartControllerTests { public void testPendingIntent_allowedByCallerAndRealCallerButOptOut_isBlocked() { // setup state BalVerdict callerVerdict = - new BalVerdict(BAL_ALLOW_PERMISSION, false, "CallerhasPermission"); + new BalVerdict(BAL_ALLOW_PERMISSION, "CallerhasPermission"); BalVerdict realCallerVerdict = - new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "RealCallerIsVisible"); + new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "RealCallerIsVisible"); mController.setCallerVerdict(callerVerdict); mController.setRealCallerVerdict(realCallerVerdict); @@ -452,7 +452,7 @@ public class BackgroundActivityStartControllerTests { public void testPendingIntent_allowedByCallerAndOptIn_isAllowed() { // setup state BalVerdict callerVerdict = - new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "CallerIsVisible"); + new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "CallerIsVisible"); mController.setCallerVerdict(callerVerdict); mController.setRealCallerVerdict(BalVerdict.BLOCK); @@ -489,7 +489,7 @@ public class BackgroundActivityStartControllerTests { public void testPendingIntent_allowedByRealCallerAndOptIn_isAllowed() { // setup state BalVerdict realCallerVerdict = - new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "RealCallerIsVisible"); + new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, "RealCallerIsVisible"); mController.setCallerVerdict(BalVerdict.BLOCK); mController.setRealCallerVerdict(realCallerVerdict); @@ -571,7 +571,6 @@ public class BackgroundActivityStartControllerTests { + "callerApp: mCallerApp; " + "inVisibleTask: false; " + "balAllowedByPiCreator: BSP.ALLOW_BAL; " - + "balAllowedByPiCreatorWithHardening: BSP.ALLOW_BAL; " + "callerStartMode: MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; " + "hasRealCaller: true; " + "isCallForResult: false; " @@ -674,7 +673,6 @@ public class BackgroundActivityStartControllerTests { + "callerApp: mCallerApp; " + "inVisibleTask: false; " + "balAllowedByPiCreator: BSP.NONE; " - + "balAllowedByPiCreatorWithHardening: BSP.NONE; " + "callerStartMode: MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; " + "hasRealCaller: true; " + "isCallForResult: false; " diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index a97f9a837bab..3cccbc419425 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -41,6 +41,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -1088,6 +1089,10 @@ public class VcnManagementServiceTest { @Test public void testGetRestrictedTransportsFromCarrierConfig() { + assumeTrue( + "Configuring restricted transport types is only allowed on a debuggable build", + Build.isDebuggable()); + final Set<Integer> restrictedTransports = new ArraySet<>(); restrictedTransports.add(TRANSPORT_CELLULAR); restrictedTransports.add(TRANSPORT_WIFI); @@ -1109,6 +1114,10 @@ public class VcnManagementServiceTest { @Test public void testGetRestrictedTransportsFromCarrierConfig_noRestrictPolicyConfigured() { + assumeTrue( + "Configuring restricted transport types is only allowed on a debuggable build", + Build.isDebuggable()); + final Set<Integer> restrictedTransports = Collections.singleton(TRANSPORT_WIFI); final PersistableBundleWrapper carrierConfig = @@ -1123,6 +1132,10 @@ public class VcnManagementServiceTest { @Test public void testGetRestrictedTransportsFromCarrierConfig_noCarrierConfig() { + assumeTrue( + "Configuring restricted transport types is only allowed on a debuggable build", + Build.isDebuggable()); + final Set<Integer> restrictedTransports = Collections.singleton(TRANSPORT_WIFI); final TelephonySubscriptionSnapshot lastSnapshot = @@ -1134,6 +1147,10 @@ public class VcnManagementServiceTest { @Test public void testGetRestrictedTransportsFromCarrierConfigAndVcnConfig() { + assumeTrue( + "Configuring restricted transport types is only allowed on a debuggable build", + Build.isDebuggable()); + // Configure restricted transport in CarrierConfig final Set<Integer> restrictedTransportInCarrierConfig = Collections.singleton(TRANSPORT_WIFI); |