diff options
110 files changed, 7188 insertions, 1139 deletions
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 15b13dc97554..ffb920b907ab 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -757,6 +757,15 @@ interface IActivityManager { void addStartInfoTimestamp(int key, long timestampNs, int userId); /** + * Reports view related timestamps to be added to the calling apps most + * recent {@link ApplicationStartInfo}. + * + * @param renderThreadDrawStartTimeNs Clock monotonic time in nanoseconds of RenderThread draw start + * @param framePresentedTimeNs Clock monotonic time in nanoseconds of frame presented + */ + oneway void reportStartInfoViewTimestamps(long renderThreadDrawStartTimeNs, long framePresentedTimeNs); + + /** * Return a list of {@link ApplicationExitInfo} records. * * <p class="note"> Note: System stores these historical information in a ring buffer, older diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java index b41753413baf..7bdd53d00215 100644 --- a/core/java/android/os/BatteryConsumer.java +++ b/core/java/android/os/BatteryConsumer.java @@ -197,6 +197,9 @@ public abstract class BatteryConsumer { POWER_COMPONENT_MOBILE_RADIO, POWER_COMPONENT_WIFI, POWER_COMPONENT_BLUETOOTH, + POWER_COMPONENT_AUDIO, + POWER_COMPONENT_VIDEO, + POWER_COMPONENT_FLASHLIGHT, }; static final int COLUMN_INDEX_BATTERY_CONSUMER_TYPE = 0; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index beb1a5d4ee15..cb82278ca577 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -177,6 +177,7 @@ import android.graphics.Region; import android.graphics.RenderNode; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; +import android.hardware.SyncFence; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.hardware.display.DisplayManagerGlobal; @@ -218,6 +219,7 @@ import android.util.proto.ProtoOutputStream; import android.view.InputDevice.InputSourceClass; import android.view.Surface.OutOfResourcesException; import android.view.SurfaceControl.Transaction; +import android.view.SurfaceControl.TransactionStats; import android.view.View.AttachInfo; import android.view.View.FocusDirection; import android.view.View.MeasureSpec; @@ -292,6 +294,7 @@ import java.util.OptionalInt; import java.util.Queue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; +import java.util.function.Consumer; import java.util.function.Predicate; /** * The top of a view hierarchy, implementing the needed protocol between View @@ -1188,6 +1191,13 @@ public final class ViewRootImpl implements ViewParent, private String mFpsTraceName; private String mLargestViewTraceName; + private final boolean mAppStartInfoTimestampsFlagValue; + @GuardedBy("this") + private boolean mAppStartTimestampsSent = false; + private boolean mAppStartTrackingStarted = false; + private long mRenderThreadDrawStartTimeNs = -1; + private long mFirstFramePresentedTimeNs = -1; + private static boolean sToolkitSetFrameRateReadOnlyFlagValue; private static boolean sToolkitFrameRateFunctionEnablingReadOnlyFlagValue; private static boolean sToolkitMetricsForFrameRateDecisionFlagValue; @@ -1304,6 +1314,8 @@ public final class ViewRootImpl implements ViewParent, } else { mSensitiveContentProtectionService = null; } + + mAppStartInfoTimestampsFlagValue = android.app.Flags.appStartInfoTimestamps(); } public static void addFirstDrawHandler(Runnable callback) { @@ -2576,6 +2588,12 @@ public final class ViewRootImpl implements ViewParent, notifySurfaceDestroyed(); } destroySurface(); + + // Reset so they can be sent again for warm starts. + mAppStartTimestampsSent = false; + mAppStartTrackingStarted = false; + mRenderThreadDrawStartTimeNs = -1; + mFirstFramePresentedTimeNs = -1; } } } @@ -4374,6 +4392,30 @@ public final class ViewRootImpl implements ViewParent, reportDrawFinished(t, seqId); } }); + + // Only trigger once per {@link ViewRootImpl} instance, so don't add listener if + // {link mTransactionCompletedTimeNs} has already been set. + if (mAppStartInfoTimestampsFlagValue && !mAppStartTrackingStarted) { + mAppStartTrackingStarted = true; + Transaction transaction = new Transaction(); + transaction.addTransactionCompletedListener(mExecutor, + new Consumer<TransactionStats>() { + @Override + public void accept(TransactionStats transactionStats) { + SyncFence presentFence = transactionStats.getPresentFence(); + if (presentFence.awaitForever()) { + if (mFirstFramePresentedTimeNs == -1) { + // Only trigger once per {@link ViewRootImpl} instance. + mFirstFramePresentedTimeNs = presentFence.getSignalTime(); + maybeSendAppStartTimes(); + } + } + presentFence.close(); + } + }); + applyTransactionOnDraw(transaction); + } + if (DEBUG_BLAST) { Log.d(mTag, "Setup new sync=" + mWmsRequestSyncGroup.getName()); } @@ -4381,6 +4423,45 @@ public final class ViewRootImpl implements ViewParent, mWmsRequestSyncGroup.add(this, null /* runnable */); } + private void maybeSendAppStartTimes() { + synchronized (this) { + if (mAppStartTimestampsSent) { + // Don't send timestamps more than once. + return; + } + + // If we already have {@link mRenderThreadDrawStartTimeNs} then pass it through, if not + // post to main thread and check if we have it there. + if (mRenderThreadDrawStartTimeNs != -1) { + sendAppStartTimesLocked(); + } else { + mHandler.post(new Runnable() { + @Override + public void run() { + synchronized (ViewRootImpl.this) { + if (mRenderThreadDrawStartTimeNs == -1) { + return; + } + sendAppStartTimesLocked(); + } + } + }); + } + } + } + + @GuardedBy("this") + private void sendAppStartTimesLocked() { + try { + ActivityManager.getService().reportStartInfoViewTimestamps( + mRenderThreadDrawStartTimeNs, mFirstFramePresentedTimeNs); + mAppStartTimestampsSent = true; + } catch (RemoteException e) { + // Ignore, timestamps may be lost. + if (DBG) Log.d(TAG, "Exception attempting to report start timestamps.", e); + } + } + /** * Helper used to notify the service to block projection when a sensitive * view (the view displays sensitive content) is attached to the window. @@ -5567,7 +5648,13 @@ public final class ViewRootImpl implements ViewParent, registerCallbackForPendingTransactions(); } + long timeNs = SystemClock.uptimeNanos(); mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this); + + // Only trigger once per {@link ViewRootImpl} instance. + if (mAppStartInfoTimestampsFlagValue && mRenderThreadDrawStartTimeNs == -1) { + mRenderThreadDrawStartTimeNs = timeNs; + } } else { // If we get here with a disabled & requested hardware renderer, something went // wrong (an invalidate posted right before we destroyed the hardware surface diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java index 244165f5e814..5c270e0e874c 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistory.java +++ b/core/java/com/android/internal/os/BatteryStatsHistory.java @@ -1514,6 +1514,36 @@ public class BatteryStatsHistory { } /** + * Records an event when some state2 flag changes to true. + */ + public void recordState2StartEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags, + int uid, String name) { + synchronized (this) { + mHistoryCur.states2 |= stateFlags; + mHistoryCur.eventCode = EVENT_STATE_CHANGE | EVENT_FLAG_START; + mHistoryCur.eventTag = mHistoryCur.localEventTag; + mHistoryCur.eventTag.uid = uid; + mHistoryCur.eventTag.string = name; + writeHistoryItem(elapsedRealtimeMs, uptimeMs); + } + } + + /** + * Records an event when some state2 flag changes to false. + */ + public void recordState2StopEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags, + int uid, String name) { + synchronized (this) { + mHistoryCur.states2 &= ~stateFlags; + mHistoryCur.eventCode = EVENT_STATE_CHANGE | EVENT_FLAG_FINISH; + mHistoryCur.eventTag = mHistoryCur.localEventTag; + mHistoryCur.eventTag.uid = uid; + mHistoryCur.eventTag.string = name; + writeHistoryItem(elapsedRealtimeMs, uptimeMs); + } + } + + /** * Records an event when some state2 flag changes to false. */ public void recordState2StopEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) { 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 55f2dee95a34..00262be06623 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java +++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java @@ -30,7 +30,7 @@ public class CoreDocument { ArrayList<Operation> mOperations; RemoteComposeState mRemoteComposeState = new RemoteComposeState(); - + TimeVariables mTimeVariables = new TimeVariables(); // Semantic version of the document Version mVersion = new Version(0, 1, 0); @@ -70,6 +70,7 @@ public class CoreDocument { public void setWidth(int width) { this.mWidth = width; + mRemoteComposeState.setWindowWidth(width); } public int getHeight() { @@ -78,6 +79,8 @@ public class CoreDocument { public void setHeight(int height) { this.mHeight = height; + mRemoteComposeState.setWindowHeight(height); + } public RemoteComposeBuffer getBuffer() { @@ -111,21 +114,21 @@ public class CoreDocument { /** * Sets the way the player handles the content * - * @param scroll set the horizontal behavior (NONE|SCROLL_HORIZONTAL|SCROLL_VERTICAL) + * @param scroll set the horizontal behavior (NONE|SCROLL_HORIZONTAL|SCROLL_VERTICAL) * @param alignment set the alignment of the content (TOP|CENTER|BOTTOM|START|END) - * @param sizing set the type of sizing for the content (NONE|SIZING_LAYOUT|SIZING_SCALE) - * @param mode set the mode of sizing, either LAYOUT modes or SCALE modes - * the LAYOUT modes are: - * - LAYOUT_MATCH_PARENT - * - LAYOUT_WRAP_CONTENT - * or adding an horizontal mode and a vertical mode: - * - LAYOUT_HORIZONTAL_MATCH_PARENT - * - LAYOUT_HORIZONTAL_WRAP_CONTENT - * - LAYOUT_HORIZONTAL_FIXED - * - LAYOUT_VERTICAL_MATCH_PARENT - * - LAYOUT_VERTICAL_WRAP_CONTENT - * - LAYOUT_VERTICAL_FIXED - * The LAYOUT_*_FIXED modes will use the intrinsic document size + * @param sizing set the type of sizing for the content (NONE|SIZING_LAYOUT|SIZING_SCALE) + * @param mode set the mode of sizing, either LAYOUT modes or SCALE modes + * the LAYOUT modes are: + * - LAYOUT_MATCH_PARENT + * - LAYOUT_WRAP_CONTENT + * or adding an horizontal mode and a vertical mode: + * - LAYOUT_HORIZONTAL_MATCH_PARENT + * - LAYOUT_HORIZONTAL_WRAP_CONTENT + * - LAYOUT_HORIZONTAL_FIXED + * - LAYOUT_VERTICAL_MATCH_PARENT + * - LAYOUT_VERTICAL_WRAP_CONTENT + * - LAYOUT_VERTICAL_FIXED + * The LAYOUT_*_FIXED modes will use the intrinsic document size */ public void setRootContentBehavior(int scroll, int alignment, int sizing, int mode) { this.mContentScroll = scroll; @@ -138,8 +141,8 @@ public class CoreDocument { * Given dimensions w x h of where to paint the content, returns the corresponding scale factor * according to the contentSizing information * - * @param w horizontal dimension of the rendering area - * @param h vertical dimension of the rendering area + * @param w horizontal dimension of the rendering area + * @param h vertical dimension of the rendering area * @param scaleOutput will contain the computed scale factor */ public void computeScale(float w, float h, float[] scaleOutput) { @@ -154,37 +157,43 @@ public class CoreDocument { float scale = Math.min(1f, Math.min(scaleX, scaleY)); contentScaleX = scale; contentScaleY = scale; - } break; + } + break; case RootContentBehavior.SCALE_FIT: { float scaleX = w / mWidth; float scaleY = h / mHeight; float scale = Math.min(scaleX, scaleY); contentScaleX = scale; contentScaleY = scale; - } break; + } + break; case RootContentBehavior.SCALE_FILL_WIDTH: { float scale = w / mWidth; contentScaleX = scale; contentScaleY = scale; - } break; + } + break; case RootContentBehavior.SCALE_FILL_HEIGHT: { float scale = h / mHeight; contentScaleX = scale; contentScaleY = scale; - } break; + } + break; case RootContentBehavior.SCALE_CROP: { float scaleX = w / mWidth; float scaleY = h / mHeight; float scale = Math.max(scaleX, scaleY); contentScaleX = scale; contentScaleY = scale; - } break; + } + break; case RootContentBehavior.SCALE_FILL_BOUNDS: { float scaleX = w / mWidth; float scaleY = h / mHeight; contentScaleX = scaleX; contentScaleY = scaleY; - } break; + } + break; default: // nothing } @@ -197,10 +206,10 @@ public class CoreDocument { * Given dimensions w x h of where to paint the content, returns the corresponding translation * according to the contentAlignment information * - * @param w horizontal dimension of the rendering area - * @param h vertical dimension of the rendering area - * @param contentScaleX the horizontal scale we are going to use for the content - * @param contentScaleY the vertical scale we are going to use for the content + * @param w horizontal dimension of the rendering area + * @param h vertical dimension of the rendering area + * @param contentScaleX the horizontal scale we are going to use for the content + * @param contentScaleY the vertical scale we are going to use for the content * @param translateOutput will contain the computed translation */ private void computeTranslate(float w, float h, float contentScaleX, float contentScaleY, @@ -215,26 +224,32 @@ public class CoreDocument { switch (horizontalContentAlignment) { case RootContentBehavior.ALIGNMENT_START: { // nothing - } break; + } + break; case RootContentBehavior.ALIGNMENT_HORIZONTAL_CENTER: { translateX = (w - contentWidth) / 2f; - } break; + } + break; case RootContentBehavior.ALIGNMENT_END: { translateX = w - contentWidth; - } break; + } + break; default: // nothing (same as alignment_start) } switch (verticalContentAlignment) { case RootContentBehavior.ALIGNMENT_TOP: { // nothing - } break; + } + break; case RootContentBehavior.ALIGNMENT_VERTICAL_CENTER: { translateY = (h - contentHeight) / 2f; - } break; + } + break; case RootContentBehavior.ALIGNMENT_BOTTOM: { translateY = h - contentHeight; - } break; + } + break; default: // nothing (same as alignment_top) } @@ -291,7 +306,13 @@ public class CoreDocument { this.mMetadata = metadata; } - public boolean contains(float x, float y) { + /** + * Returns true if x,y coordinate is within bounds + * @param x x-coordinate + * @param y y-coordinate + * @return x,y coordinate is within bounds + */ + public boolean contains(float x, float y) { return x >= mLeft && x < mRight && y >= mTop && y < mBottom; } @@ -341,16 +362,22 @@ public class CoreDocument { public void initializeContext(RemoteContext context) { mRemoteComposeState.reset(); mClickAreas.clear(); - + mRemoteComposeState.setNextId(RemoteComposeState.START_ID); context.mDocument = this; context.mRemoteComposeState = mRemoteComposeState; - // mark context to be in DATA mode, which will skip the painting ops. context.mMode = RemoteContext.ContextMode.DATA; - for (Operation op: mOperations) { + mTimeVariables.updateTime(context); + + for (Operation op : mOperations) { + if (op instanceof VariableSupport) { + ((VariableSupport) op).updateVariables(context); + ((VariableSupport) op).registerListening(context); + } op.apply(context); } context.mMode = RemoteContext.ContextMode.UNSET; + } /////////////////////////////////////////////////////////////////////////////////////////////// @@ -375,7 +402,7 @@ public class CoreDocument { * @param minorVersion minor version number, increased when adding new features * @param patch patch level, increased upon bugfixes */ - void setVersion(int majorVersion, int minorVersion, int patch) { + void setVersion(int majorVersion, int minorVersion, int patch) { mVersion = new Version(majorVersion, minorVersion, patch); } @@ -389,13 +416,13 @@ public class CoreDocument { * the click coordinates will be the one reported; the order of addition of those click areas * is therefore meaningful. * - * @param id the id of the area, which will be reported on click + * @param id the id of the area, which will be reported on click * @param contentDescription the content description (used for accessibility) - * @param left the left coordinate of the click area (in pixels) - * @param top the top coordinate of the click area (in pixels) - * @param right the right coordinate of the click area (in pixels) - * @param bottom the bottom coordinate of the click area (in pixels) - * @param metadata arbitrary metadata associated with the are, also reported on click + * @param left the left coordinate of the click area (in pixels) + * @param top the top coordinate of the click area (in pixels) + * @param right the right coordinate of the click area (in pixels) + * @param bottom the bottom coordinate of the click area (in pixels) + * @param metadata arbitrary metadata associated with the are, also reported on click */ public void addClickArea(int id, String contentDescription, float left, float top, float right, float bottom, String metadata) { @@ -417,7 +444,7 @@ public class CoreDocument { * listeners. */ public void onClick(float x, float y) { - for (ClickAreaRepresentation clickArea: mClickAreas) { + for (ClickAreaRepresentation clickArea : mClickAreas) { if (clickArea.contains(x, y)) { warnClickListeners(clickArea); } @@ -430,7 +457,7 @@ public class CoreDocument { * @param id the click area id */ public void performClick(int id) { - for (ClickAreaRepresentation clickArea: mClickAreas) { + for (ClickAreaRepresentation clickArea : mClickAreas) { if (clickArea.mId == id) { warnClickListeners(clickArea); } @@ -441,17 +468,36 @@ public class CoreDocument { * Warn click listeners when a click area is activated */ private void warnClickListeners(ClickAreaRepresentation clickArea) { - for (ClickCallbacks listener: mClickListeners) { + for (ClickCallbacks listener : mClickListeners) { listener.click(clickArea.mId, clickArea.mMetadata); } } - /////////////////////////////////////////////////////////////////////////////////////////////// + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + for (Operation op : mOperations) { + builder.append(op.toString()); + builder.append("\n"); + } + return builder.toString(); + } + + ////////////////////////////////////////////////////////////////////////// // Painting - /////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////// private final float[] mScaleOutput = new float[2]; private final float[] mTranslateOutput = new float[2]; + private int mRepaintNext = -1; // delay to next repaint -1 = don't 1 = asap + + /** + * Returns > 0 if it needs to repaint + * @return + */ + public int needsRepaint() { + return mRepaintNext; + } /** * Paint the document @@ -475,6 +521,11 @@ public class CoreDocument { context.mPaintContext.translate(mTranslateOutput[0], mTranslateOutput[1]); context.mPaintContext.scale(mScaleOutput[0], mScaleOutput[1]); } + mTimeVariables.updateTime(context); + context.loadFloat(RemoteContext.ID_WINDOW_WIDTH, getWidth()); + context.loadFloat(RemoteContext.ID_WINDOW_HEIGHT, getHeight()); + mRepaintNext = context.updateOps(); + for (Operation op : mOperations) { // operations will only be executed if no theme is set (ie UNSPECIFIED) // or the theme is equal as the one passed in argument to paint. 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 54b277a2ac58..4b45ab691215 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java +++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java @@ -19,6 +19,7 @@ import com.android.internal.widget.remotecompose.core.operations.BitmapData; import com.android.internal.widget.remotecompose.core.operations.ClickArea; import com.android.internal.widget.remotecompose.core.operations.ClipPath; import com.android.internal.widget.remotecompose.core.operations.ClipRect; +import com.android.internal.widget.remotecompose.core.operations.ColorExpression; import com.android.internal.widget.remotecompose.core.operations.DrawArc; import com.android.internal.widget.remotecompose.core.operations.DrawBitmap; import com.android.internal.widget.remotecompose.core.operations.DrawBitmapInt; @@ -28,9 +29,12 @@ import com.android.internal.widget.remotecompose.core.operations.DrawOval; import com.android.internal.widget.remotecompose.core.operations.DrawPath; import com.android.internal.widget.remotecompose.core.operations.DrawRect; import com.android.internal.widget.remotecompose.core.operations.DrawRoundRect; +import com.android.internal.widget.remotecompose.core.operations.DrawText; +import com.android.internal.widget.remotecompose.core.operations.DrawTextAnchored; import com.android.internal.widget.remotecompose.core.operations.DrawTextOnPath; -import com.android.internal.widget.remotecompose.core.operations.DrawTextRun; import com.android.internal.widget.remotecompose.core.operations.DrawTweenPath; +import com.android.internal.widget.remotecompose.core.operations.FloatConstant; +import com.android.internal.widget.remotecompose.core.operations.FloatExpression; import com.android.internal.widget.remotecompose.core.operations.Header; import com.android.internal.widget.remotecompose.core.operations.MatrixRestore; import com.android.internal.widget.remotecompose.core.operations.MatrixRotate; @@ -42,7 +46,10 @@ import com.android.internal.widget.remotecompose.core.operations.PaintData; import com.android.internal.widget.remotecompose.core.operations.PathData; import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior; import com.android.internal.widget.remotecompose.core.operations.RootContentDescription; +import com.android.internal.widget.remotecompose.core.operations.ShaderData; import com.android.internal.widget.remotecompose.core.operations.TextData; +import com.android.internal.widget.remotecompose.core.operations.TextFromFloat; +import com.android.internal.widget.remotecompose.core.operations.TextMerge; import com.android.internal.widget.remotecompose.core.operations.Theme; import com.android.internal.widget.remotecompose.core.operations.utilities.IntMap; @@ -67,9 +74,10 @@ public class Operations { public static final int DRAW_BITMAP = 44; public static final int DRAW_BITMAP_INT = 66; public static final int DATA_BITMAP = 101; + public static final int DATA_SHADER = 45; public static final int DATA_TEXT = 102; -/////////////////////////////===================== + /////////////////////////////===================== public static final int CLIP_PATH = 38; public static final int CLIP_RECT = 39; public static final int PAINT_VALUES = 40; @@ -91,6 +99,12 @@ public class Operations { public static final int MATRIX_SAVE = 130; public static final int MATRIX_RESTORE = 131; public static final int MATRIX_SET = 132; + public static final int DATA_FLOAT = 80; + public static final int ANIMATED_FLOAT = 81; + public static final int DRAW_TEXT_ANCHOR = 133; + public static final int COLOR_EXPRESSIONS = 134; + public static final int TEXT_FROM_FLOAT = 135; + public static final int TEXT_MERGE = 136; /////////////////////////////////////////====================== public static IntMap<CompanionOperation> map = new IntMap<>(); @@ -114,7 +128,7 @@ public class Operations { map.put(DRAW_RECT, DrawRect.COMPANION); map.put(DRAW_ROUND_RECT, DrawRoundRect.COMPANION); map.put(DRAW_TEXT_ON_PATH, DrawTextOnPath.COMPANION); - map.put(DRAW_TEXT_RUN, DrawTextRun.COMPANION); + map.put(DRAW_TEXT_RUN, DrawText.COMPANION); map.put(DRAW_TWEEN_PATH, DrawTweenPath.COMPANION); map.put(DATA_PATH, PathData.COMPANION); map.put(PAINT_VALUES, PaintData.COMPANION); @@ -126,6 +140,13 @@ public class Operations { map.put(MATRIX_TRANSLATE, MatrixTranslate.COMPANION); map.put(CLIP_PATH, ClipPath.COMPANION); map.put(CLIP_RECT, ClipRect.COMPANION); + map.put(DATA_SHADER, ShaderData.COMPANION); + map.put(DATA_FLOAT, FloatConstant.COMPANION); + map.put(ANIMATED_FLOAT, FloatExpression.COMPANION); + map.put(DRAW_TEXT_ANCHOR, DrawTextAnchored.COMPANION); + map.put(COLOR_EXPRESSIONS, ColorExpression.COMPANION); + map.put(TEXT_FROM_FLOAT, TextFromFloat.COMPANION); + map.put(TEXT_MERGE, TextMerge.COMPANION); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java index eece8ad52b60..ecd0efceacf3 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java +++ b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java @@ -68,7 +68,35 @@ public abstract class PaintContext { public abstract void drawTextOnPath(int textId, int pathId, float hOffset, float vOffset); - public abstract void drawTextRun(int textID, + /** + * Return the dimensions (left, top, right, bottom). + * Relative to a drawTextRun x=0, y=0; + * + * @param textId + * @param start + * @param end if end is -1 it means the whole string + * @param monospace measure with better support for monospace + * @param bounds the bounds (left, top, right, bottom) + */ + public abstract void getTextBounds(int textId, + int start, + int end, + boolean monospace, + float[]bounds); + + /** + * Draw a text starting ast x,y + * + * @param textId reference to the text + * @param start + * @param end + * @param contextStart + * @param contextEnd + * @param x + * @param y + * @param rtl + */ + public abstract void drawTextRun(int textId, int start, int end, int contextStart, @@ -77,6 +105,14 @@ public abstract class PaintContext { float y, boolean rtl); + /** + * Draw an interpolation between two paths + * @param path1Id + * @param path2Id + * @param tween 0.0 = is path1 1.0 is path2 + * @param start + * @param stop + */ public abstract void drawTweenPath(int path1Id, int path2Id, float tween, @@ -85,21 +121,70 @@ public abstract class PaintContext { public abstract void applyPaint(PaintBundle mPaintData); - public abstract void mtrixScale(float scaleX, float scaleY, float centerX, float centerY); - + /** + * Scale the rendering by scaleX and saleY (1.0 = no scale). + * Scaling is done about centerX,centerY. + * + * @param scaleX + * @param scaleY + * @param centerX + * @param centerY + */ + public abstract void matrixScale(float scaleX, float scaleY, float centerX, float centerY); + + /** + * Translate the rendering + * @param translateX + * @param translateY + */ public abstract void matrixTranslate(float translateX, float translateY); + /** + * Skew the rendering + * @param skewX + * @param skewY + */ public abstract void matrixSkew(float skewX, float skewY); + /** + * Rotate the rendering. + * Note rotates are cumulative. + * @param rotate angle to rotate + * @param pivotX x-coordinate about which to rotate + * @param pivotY y-coordinate about which to rotate + */ public abstract void matrixRotate(float rotate, float pivotX, float pivotY); + /** + * Save the current state of the transform + */ public abstract void matrixSave(); + /** + * Restore the previously saved state of the transform + */ public abstract void matrixRestore(); + /** + * Set the clip to a rectangle. + * Drawing outside the current clip region will have no effect + * @param left + * @param top + * @param right + * @param bottom + */ public abstract void clipRect(float left, float top, float right, float bottom); + /** + * Clip based on a path. + * @param pathId + * @param regionOp + */ public abstract void clipPath(int pathId, int regionOp); + /** + * Reset the paint + */ + public abstract void reset(); } 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 c2e81318c09a..52fc3143d721 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java @@ -19,6 +19,7 @@ import com.android.internal.widget.remotecompose.core.operations.BitmapData; import com.android.internal.widget.remotecompose.core.operations.ClickArea; import com.android.internal.widget.remotecompose.core.operations.ClipPath; import com.android.internal.widget.remotecompose.core.operations.ClipRect; +import com.android.internal.widget.remotecompose.core.operations.ColorExpression; import com.android.internal.widget.remotecompose.core.operations.DrawArc; import com.android.internal.widget.remotecompose.core.operations.DrawBitmap; import com.android.internal.widget.remotecompose.core.operations.DrawBitmapInt; @@ -28,9 +29,12 @@ import com.android.internal.widget.remotecompose.core.operations.DrawOval; import com.android.internal.widget.remotecompose.core.operations.DrawPath; import com.android.internal.widget.remotecompose.core.operations.DrawRect; import com.android.internal.widget.remotecompose.core.operations.DrawRoundRect; +import com.android.internal.widget.remotecompose.core.operations.DrawText; +import com.android.internal.widget.remotecompose.core.operations.DrawTextAnchored; import com.android.internal.widget.remotecompose.core.operations.DrawTextOnPath; -import com.android.internal.widget.remotecompose.core.operations.DrawTextRun; import com.android.internal.widget.remotecompose.core.operations.DrawTweenPath; +import com.android.internal.widget.remotecompose.core.operations.FloatConstant; +import com.android.internal.widget.remotecompose.core.operations.FloatExpression; import com.android.internal.widget.remotecompose.core.operations.Header; import com.android.internal.widget.remotecompose.core.operations.MatrixRestore; import com.android.internal.widget.remotecompose.core.operations.MatrixRotate; @@ -43,8 +47,12 @@ import com.android.internal.widget.remotecompose.core.operations.PathData; import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior; import com.android.internal.widget.remotecompose.core.operations.RootContentDescription; import com.android.internal.widget.remotecompose.core.operations.TextData; +import com.android.internal.widget.remotecompose.core.operations.TextFromFloat; +import com.android.internal.widget.remotecompose.core.operations.TextMerge; import com.android.internal.widget.remotecompose.core.operations.Theme; +import com.android.internal.widget.remotecompose.core.operations.Utils; import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle; +import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation; import java.io.File; import java.io.FileInputStream; @@ -58,9 +66,20 @@ import java.util.Arrays; * Provides an abstract buffer to encode/decode RemoteCompose operations */ public class RemoteComposeBuffer { + public static final int EASING_CUBIC_STANDARD = FloatAnimation.CUBIC_STANDARD; + public static final int EASING_CUBIC_ACCELERATE = FloatAnimation.CUBIC_ACCELERATE; + public static final int EASING_CUBIC_DECELERATE = FloatAnimation.CUBIC_DECELERATE; + public static final int EASING_CUBIC_LINEAR = FloatAnimation.CUBIC_LINEAR; + public static final int EASING_CUBIC_ANTICIPATE = FloatAnimation.CUBIC_ANTICIPATE; + public static final int EASING_CUBIC_OVERSHOOT = FloatAnimation.CUBIC_OVERSHOOT; + public static final int EASING_CUBIC_CUSTOM = FloatAnimation.CUBIC_CUSTOM; + public static final int EASING_SPLINE_CUSTOM = FloatAnimation.SPLINE_CUSTOM; + public static final int EASING_EASE_OUT_BOUNCE = FloatAnimation.EASE_OUT_BOUNCE; + public static final int EASING_EASE_OUT_ELASTIC = FloatAnimation.EASE_OUT_ELASTIC; WireBuffer mBuffer = new WireBuffer(); Platform mPlatform = null; RemoteComposeState mRemoteComposeState; + private static final boolean DEBUG = false; /** * Provides an abstract buffer to encode/decode RemoteCompose operations @@ -171,7 +190,7 @@ public class RemoteComposeBuffer { * * @param text the string to inject in the buffer */ - int addText(String text) { + public int addText(String text) { int id = mRemoteComposeState.dataGetId(text); if (id == -1) { id = mRemoteComposeState.cache(text); @@ -350,7 +369,6 @@ public class RemoteComposeBuffer { addDrawPath(id); } - /** * Draw the specified path * @@ -426,12 +444,160 @@ public class RemoteComposeBuffer { float y, boolean rtl) { int textId = addText(text); - DrawTextRun.COMPANION.apply( + DrawText.COMPANION.apply( mBuffer, textId, start, end, contextStart, contextEnd, x, y, rtl); } /** + * Draw the text, with origin at (x,y). The origin is interpreted + * based on the Align setting in the paint. + * + * @param textId The text to be drawn + * @param start The index of the first character in text to draw + * @param end (end - 1) is the index of the last character in text to draw + * @param contextStart + * @param contextEnd + * @param x The x-coordinate of the origin of the text being drawn + * @param y The y-coordinate of the baseline of the text being drawn + * @param rtl Draw RTTL + */ + public void addDrawTextRun(int textId, + int start, + int end, + int contextStart, + int contextEnd, + float x, + float y, + boolean rtl) { + DrawText.COMPANION.apply( + mBuffer, textId, start, end, + contextStart, contextEnd, x, y, rtl); + } + + /** + * Draw a text on canvas at relative to position (x, y), + * offset panX and panY. + * <br> + * The panning factors (panX, panY) mapped to the + * resulting bounding box of the text, in such a way that a + * panning factor of (0.0, 0.0) would center the text at (x, y) + * <ul> + * <li> Panning of -1.0, -1.0 - the text above & right of x,y.</li> + * <li>Panning of 1.0, 1.0 - the text is below and to the left</li> + * <li>Panning of 1.0, 0.0 - the test is centered & to the right of x,y</li> + * </ul> + * Setting panY to NaN results in y being the baseline of the text. + * + * @param text text to draw + * @param x Coordinate of the Anchor + * @param y Coordinate of the Anchor + * @param panX justifies text -1.0=right, 0.0=center, 1.0=left + * @param panY position text -1.0=above, 0.0=center, 1.0=below, Nan=baseline + * @param flags 1 = RTL + */ + public void drawTextAnchored(String text, + float x, + float y, + float panX, + float panY, + int flags) { + int textId = addText(text); + DrawTextAnchored.COMPANION.apply( + mBuffer, textId, + x, y, + panX, panY, + flags); + } + + /** + * Add a text and id so that it can be used + * + * @param text + * @return + */ + public int createTextId(String text) { + return addText(text); + } + + /** + * Merge two text (from id's) output one id + * @param id1 left id + * @param id2 right id + * @return new id that merges the two text + */ + public int textMerge(int id1, int id2) { + int textId = addText(id1 + "+" + id2); + TextMerge.COMPANION.apply(mBuffer, textId, id1, id2); + return textId; + } + + public static final int PAD_AFTER_SPACE = TextFromFloat.PAD_AFTER_SPACE; + public static final int PAD_AFTER_NONE = TextFromFloat.PAD_AFTER_NONE; + public static final int PAD_AFTER_ZERO = TextFromFloat.PAD_AFTER_ZERO; + public static final int PAD_PRE_SPACE = TextFromFloat.PAD_PRE_SPACE; + public static final int PAD_PRE_NONE = TextFromFloat.PAD_PRE_NONE; + public static final int PAD_PRE_ZERO = TextFromFloat.PAD_PRE_ZERO; + + /** + * Create a TextFromFloat command which creates text from a Float. + * + * @param value The value to convert + * @param digitsBefore the digits before the decimal point + * @param digitsAfter the digits after the decimal point + * @param flags configure the behaviour using PAD_PRE_* and PAD_AFTER* flags + * @return id of the string that can be passed to drawTextAnchored + */ + public int createTextFromFloat(float value, short digitsBefore, + short digitsAfter, int flags) { + String placeHolder = Utils.floatToString(value) + + "(" + digitsBefore + "," + digitsAfter + "," + flags + ")"; + int id = mRemoteComposeState.dataGetId(placeHolder); + if (id == -1) { + id = mRemoteComposeState.cache(placeHolder); + // TextData.COMPANION.apply(mBuffer, id, text); + } + TextFromFloat.COMPANION.apply(mBuffer, id, value, digitsBefore, + digitsAfter, flags); + return id; + } + + /** + * Draw a text on canvas at relative to position (x, y), + * offset panX and panY. + * <br> + * The panning factors (panX, panY) mapped to the + * resulting bounding box of the text, in such a way that a + * panning factor of (0.0, 0.0) would center the text at (x, y) + * <ul> + * <li> Panning of -1.0, -1.0 - the text above & right of x,y.</li> + * <li>Panning of 1.0, 1.0 - the text is below and to the left</li> + * <li>Panning of 1.0, 0.0 - the test is centered & to the right of x,y</li> + * </ul> + * Setting panY to NaN results in y being the baseline of the text. + * + * @param textId text to draw + * @param x Coordinate of the Anchor + * @param y Coordinate of the Anchor + * @param panX justifies text -1.0=right, 0.0=center, 1.0=left + * @param panY position text -1.0=above, 0.0=center, 1.0=below, Nan=baseline + * @param flags 1 = RTL + */ + public void drawTextAnchored(int textId, + float x, + float y, + float panX, + float panY, + int flags) { + + DrawTextAnchored.COMPANION.apply( + mBuffer, textId, + x, y, + panX, panY, + flags); + } + + /** * draw an interpolation between two paths that have the same pattern * <p> * Warning paths objects are not immutable and this is not taken into consideration @@ -490,6 +656,10 @@ public class RemoteComposeBuffer { return id; } + /** + * Adds a paint Bundle to the doc + * @param paint + */ public void addPaint(PaintBundle paint) { PaintData.COMPANION.apply(mBuffer, paint); } @@ -499,7 +669,9 @@ public class RemoteComposeBuffer { mBuffer.setIndex(0); while (mBuffer.available()) { int opId = mBuffer.readByte(); - System.out.println(">>> " + opId); + if (DEBUG) { + Utils.log(">> " + opId); + } CompanionOperation operation = Operations.map.get(opId); if (operation == null) { throw new RuntimeException("Unknown operation encountered " + opId); @@ -519,7 +691,6 @@ public class RemoteComposeBuffer { Theme.COMPANION.apply(mBuffer, theme); } - static String version() { return "v1.0"; } @@ -654,8 +825,8 @@ public class RemoteComposeBuffer { /** * Add a pre-concat of the current matrix with the specified scale. * - * @param scaleX The amount to scale in X - * @param scaleY The amount to scale in Y + * @param scaleX The amount to scale in X + * @param scaleY The amount to scale in Y */ public void addMatrixScale(float scaleX, float scaleY) { MatrixScale.COMPANION.apply(mBuffer, scaleX, scaleY, Float.NaN, Float.NaN); @@ -673,12 +844,174 @@ public class RemoteComposeBuffer { MatrixScale.COMPANION.apply(mBuffer, scaleX, scaleY, centerX, centerY); } + /** + * sets the clip based on clip id + * @param pathId 0 clears the clip + */ public void addClipPath(int pathId) { ClipPath.COMPANION.apply(mBuffer, pathId); } + /** + * Sets the clip based on clip rec + * @param left + * @param top + * @param right + * @param bottom + */ public void addClipRect(float left, float top, float right, float bottom) { ClipRect.COMPANION.apply(mBuffer, left, top, right, bottom); } + + /** + * Add a float return a NaN number pointing to that float + * @param value + * @return + */ + public float addFloat(float value) { + int id = mRemoteComposeState.cacheFloat(value); + FloatConstant.COMPANION.apply(mBuffer, id, value); + return Utils.asNan(id); + } + + /** + * Add a float that is a computation based on variables + * @param value A RPN style float operation i.e. "4, 3, ADD" outputs 7 + * @return NaN id of the result of the calculation + */ + public float addAnimatedFloat(float... value) { + int id = mRemoteComposeState.cache(value); + FloatExpression.COMPANION.apply(mBuffer, id, value, null); + return Utils.asNan(id); + } + + /** + * Add a float that is a computation based on variables. + * see packAnimation + * @param value A RPN style float operation i.e. "4, 3, ADD" outputs 7 + * @param animation Array of floats that represents animation + * @return NaN id of the result of the calculation + */ + public float addAnimatedFloat(float[] value, float[] animation) { + int id = mRemoteComposeState.cache(value); + FloatExpression.COMPANION.apply(mBuffer, id, value, animation); + return Utils.asNan(id); + } + + /** + * Add a color that represents the tween between two colors + * @param color1 + * @param color2 + * @param tween + * @return id of the color (color ids are short) + */ + public short addColorExpression(int color1, int color2, float tween) { + ColorExpression c = new ColorExpression(0, 0, color1, color2, tween); + short id = (short) mRemoteComposeState.cache(c); + c.mId = id; + c.write(mBuffer); + return id; + } + + /** + * Add a color that represents the tween between two colors where color1 + * is the id of a color + * @param color1 + * @param color2 + * @param tween + * @return id of the color (color ids are short) + */ + public short addColorExpression(short color1, int color2, float tween) { + ColorExpression c = new ColorExpression(0, 1, color1, color2, tween); + short id = (short) mRemoteComposeState.cache(c); + c.mId = id; + c.write(mBuffer); + return id; + } + + /** + * Add a color that represents the tween between two colors where color2 + * is the id of a color + * @param color1 + * @param color2 + * @param tween + * @return id of the color (color ids are short) + */ + public short addColorExpression(int color1, short color2, float tween) { + ColorExpression c = new ColorExpression(0, 2, color1, color2, tween); + short id = (short) mRemoteComposeState.cache(c); + c.mId = id; + c.write(mBuffer); + return id; + } + + /** + * Add a color that represents the tween between two colors where color1 & + * color2 are the ids of colors + * @param color1 + * @param color2 + * @param tween + * @return id of the color (color ids are short) + */ + public short addColorExpression(short color1, short color2, float tween) { + ColorExpression c = new ColorExpression(0, 3, color1, color2, tween); + short id = (short) mRemoteComposeState.cache(c); + c.mId = id; + c.write(mBuffer); + return id; + } + + /** + * Color calculated by Hue saturation and value. + * (as floats they can be variables used to create color transitions) + * @param hue + * @param sat + * @param value + * @return id of the color (color ids are short) + */ + public short addColorExpression(float hue, float sat, float value) { + ColorExpression c = new ColorExpression(0, hue, sat, value); + short id = (short) mRemoteComposeState.cache(c); + c.mId = id; + c.write(mBuffer); + return id; + } + + /** + * Color calculated by Alpha, Hue saturation and value. + * (as floats they can be variables used to create color transitions) + * @param alpha + * @param hue + * @param sat + * @param value + * @return id of the color (color ids are short) + */ + public short addColorExpression(int alpha, float hue, float sat, float value) { + ColorExpression c = new ColorExpression(0, alpha, hue, sat, value); + short id = (short) mRemoteComposeState.cache(c); + c.mId = id; + c.write(mBuffer); + return id; + } + + /** + * create and animation based on description and return as an array of + * floats. see addAnimatedFloat + * @param duration + * @param type + * @param spec + * @param initialValue + * @param wrap + * @return + */ + public static float[] packAnimation(float duration, + int type, + float[] spec, + float initialValue, + float wrap) { + + return FloatAnimation.packToFloatArray(duration, type, spec, initialValue, wrap); + } + } 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 17e8c839a080..66a37e677499 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java @@ -15,26 +15,53 @@ */ package com.android.internal.widget.remotecompose.core; +import static com.android.internal.widget.remotecompose.core.RemoteContext.ID_CONTINUOUS_SEC; +import static com.android.internal.widget.remotecompose.core.RemoteContext.ID_TIME_IN_MIN; +import static com.android.internal.widget.remotecompose.core.RemoteContext.ID_TIME_IN_SEC; +import static com.android.internal.widget.remotecompose.core.RemoteContext.ID_WINDOW_HEIGHT; +import static com.android.internal.widget.remotecompose.core.RemoteContext.ID_WINDOW_WIDTH; + import com.android.internal.widget.remotecompose.core.operations.utilities.IntMap; +import java.util.ArrayList; import java.util.HashMap; /** * Represents runtime state for a RemoteCompose document + * State includes things like the value of variables */ public class RemoteComposeState { - + public static final int START_ID = 42; + private static final int MAX_FLOATS = 500; private final IntMap<Object> mIntDataMap = new IntMap<>(); private final IntMap<Boolean> mIntWrittenMap = new IntMap<>(); private final HashMap<Object, Integer> mDataIntMap = new HashMap(); + private final float[] mFloatMap = new float[MAX_FLOATS]; // efficient cache + private final int[] mColorMap = new int[MAX_FLOATS]; // efficient cache + private int mNextId = START_ID; - private static int sNextId = 42; + { + for (int i = 0; i < mFloatMap.length; i++) { + mFloatMap[i] = Float.NaN; + } + } - public Object getFromId(int id) { + /** + * Get Object based on id. The system will cache things like bitmaps + * Paths etc. They can be accessed with this command + * @param id + * @return + */ + public Object getFromId(int id) { return mIntDataMap.get(id); } - public boolean containsId(int id) { + /** + * true if the cache contain this id + * @param id + * @return + */ + public boolean containsId(int id) { return mIntDataMap.get(id) != null; } @@ -69,6 +96,65 @@ public class RemoteComposeState { } /** + * Insert an item in the cache + */ + public void update(int id, Object item) { + mDataIntMap.remove(mIntDataMap.get(id)); + mDataIntMap.put(item, id); + mIntDataMap.put(id, item); + } + + /** + * Insert an item in the cache + */ + public int cacheFloat(float item) { + int id = nextId(); + mFloatMap[id] = item; + return id; + } + + /** + * Insert an item in the cache + */ + public void cacheFloat(int id, float item) { + mFloatMap[id] = item; + } + + /** + * Insert an item in the cache + */ + public void updateFloat(int id, float item) { + mFloatMap[id] = item; + } + + /** + * get float + */ + public float getFloat(int id) { + return mFloatMap[id]; + } + + /** + * Get the float value + * + * @param id + * @return + */ + public int getColor(int id) { + return mColorMap[id]; + } + + /** + * Modify the color at id. + * @param id + * @param color + */ + public void updateColor(int id, int color) { + mColorMap[id] = color; + } + + + /** * Method to determine if a cached value has been written to the documents WireBuffer based on * its id. */ @@ -79,22 +165,90 @@ public class RemoteComposeState { /** * Method to mark that a value, represented by its id, has been written to the WireBuffer */ - public void markWritten(int id) { + public void markWritten(int id) { mIntWrittenMap.put(id, true); } /** - * Clear the record of the values that have been written to the WireBuffer. + * Clear the record of the values that have been written to the WireBuffer. */ void reset() { mIntWrittenMap.clear(); } - public static int nextId() { - return sNextId++; + /** + * Get the next available id + * @return + */ + public int nextId() { + return mNextId++; } - public static void setNextId(int id) { - sNextId = id; + + /** + * Set the next id + * @param id + */ + public void setNextId(int id) { + mNextId = id; + } + + IntMap<ArrayList<VariableSupport>> mVarListeners = new IntMap<>(); + ArrayList<VariableSupport> mAllVarListeners = new ArrayList<>(); + + private void add(int id, VariableSupport variableSupport) { + ArrayList<VariableSupport> v = mVarListeners.get(id); + if (v == null) { + v = new ArrayList<VariableSupport>(); + mVarListeners.put(id, v); + } + v.add(variableSupport); + mAllVarListeners.add(variableSupport); + } + + /** + * Commands that listen to variables add themselves. + * @param id + * @param variableSupport + */ + public void listenToVar(int id, VariableSupport variableSupport) { + add(id, variableSupport); + } + + /** + * List of Commands that need to be updated + * @param context + * @return + */ + public int getOpsToUpdate(RemoteContext context) { + for (VariableSupport vs : mAllVarListeners) { + vs.updateVariables(context); + } + if (mVarListeners.get(ID_CONTINUOUS_SEC) != null) { + return 1; + } + if (mVarListeners.get(ID_TIME_IN_SEC) != null) { + return 1000; + } + if (mVarListeners.get(ID_TIME_IN_MIN) != null) { + return 1000 * 60; + } + return -1; + } + + /** + * Set the width of the overall document on screen. + * @param width + */ + public void setWindowWidth(float width) { + updateFloat(ID_WINDOW_WIDTH, width); + } + + /** + * Set the width of the overall document on screen. + * @param height + */ + public void setWindowHeight(float height) { + updateFloat(ID_WINDOW_HEIGHT, height); } } 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 d16cbc5a1a16..7e721684be0a 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,10 @@ */ package com.android.internal.widget.remotecompose.core; +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.Theme; +import com.android.internal.widget.remotecompose.core.operations.Utils; /** * Specify an abstract context used to playback RemoteCompose documents @@ -27,7 +30,7 @@ import com.android.internal.widget.remotecompose.core.operations.Theme; public abstract class RemoteContext { protected CoreDocument mDocument; public RemoteComposeState mRemoteComposeState; - + long mStart = System.nanoTime(); // todo This should be set at a hi level protected PaintContext mPaintContext = null; ContextMode mMode = ContextMode.UNSET; @@ -37,9 +40,40 @@ public abstract class RemoteContext { public float mWidth = 0f; public float mHeight = 0f; + /** + * Load a path under an id. + * Paths can be use in clip drawPath and drawTweenPath + * @param instanceId + * @param floatPath + */ public abstract void loadPathData(int instanceId, float[] floatPath); /** + * Associate a name with a give id. + * + * @param varName + * @param varId + * @param varType + */ + public abstract void loadVariableName(String varName, int varId, int varType); + + /** + * Save a color under a given id + * @param id + * @param color + */ + public abstract void loadColor(int id, int color); + + /** + * gets the time animation clock as float in seconds + * @return a monotonic time in seconds (arbitrary zero point) + */ + public float getAnimationTime() { + return (System.nanoTime() - mStart) * 1E-9f; + } + + + /** * The context can be used in a few different mode, allowing operations to skip being executed: * - UNSET : all operations will get executed * - DATA : only operations dealing with DATA (eg loading a bitmap) should execute @@ -96,6 +130,8 @@ public abstract class RemoteContext { public void header(int majorVersion, int minorVersion, int patchVersion, int width, int height, long capabilities ) { + mRemoteComposeState.setWindowWidth(width); + mRemoteComposeState.setWindowHeight(height); mDocument.setVersion(majorVersion, minorVersion, patchVersion); mDocument.setWidth(width); mDocument.setHeight(height); @@ -137,9 +173,105 @@ public abstract class RemoteContext { /////////////////////////////////////////////////////////////////////////////////////////////// // Data handling /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Save a bitmap under an imageId + * @param imageId + * @param width + * @param height + * @param bitmap + */ public abstract void loadBitmap(int imageId, int width, int height, byte[] bitmap); + + /** + * Save a string under a given id + * @param id + * @param text + */ public abstract void loadText(int id, String text); + /** + * Get a string given an id + * @param id + * @return + */ + public abstract String getText(int id); + + /** + * Load a float + * @param id + * @param value + */ + public abstract void loadFloat(int id, float value); + + /** + * Load an animated float associated with an id + * Todo: Remove? + * @param id + * @param animatedFloat + */ + public abstract void loadAnimatedFloat(int id, FloatExpression animatedFloat); + + /** + * Save a shader under and ID + * @param id + * @param value + */ + public abstract void loadShader(int id, ShaderData value); + + /** + * Get a float given an id + * @param id + * @return + */ + public abstract float getFloat(int id); + + /** + * Get the color given and ID + * @param id + * @return + */ + public abstract int getColor(int id); + + /** + * called to notify system that a command is interested in a variable + * @param id + * @param variableSupport + */ + public abstract void listensTo(int id, VariableSupport variableSupport); + + /** + * Notify commands with variables have changed + * @return + */ + public abstract int updateOps(); + + /** + * Get a shader given the id + * @param id + * @return + */ + public abstract ShaderData getShader(int id); + + public static final int ID_CONTINUOUS_SEC = 1; + public static final int ID_TIME_IN_SEC = 2; + public static final int ID_TIME_IN_MIN = 3; + public static final int ID_TIME_IN_HR = 4; + public static final int ID_WINDOW_WIDTH = 5; + public static final int ID_WINDOW_HEIGHT = 6; + public static final int ID_COMPONENT_WIDTH = 7; + public static final int ID_COMPONENT_HEIGHT = 8; + public static final int ID_CALENDAR_MONTH = 9; + + public static final float FLOAT_CONTINUOUS_SEC = Utils.asNan(ID_CONTINUOUS_SEC); + public static final float FLOAT_TIME_IN_SEC = Utils.asNan(ID_TIME_IN_SEC); + public static final float FLOAT_TIME_IN_MIN = Utils.asNan(ID_TIME_IN_MIN); + public static final float FLOAT_TIME_IN_HR = Utils.asNan(ID_TIME_IN_HR); + public static final float FLOAT_CALENDAR_MONTH = Utils.asNan(ID_CALENDAR_MONTH); + public static final float FLOAT_WINDOW_WIDTH = Utils.asNan(ID_WINDOW_WIDTH); + public static final float FLOAT_WINDOW_HEIGHT = Utils.asNan(ID_WINDOW_HEIGHT); + public static final float FLOAT_COMPONENT_WIDTH = Utils.asNan(ID_COMPONENT_WIDTH); + public static final float FLOAT_COMPONENT_HEIGHT = Utils.asNan(ID_COMPONENT_HEIGHT); /////////////////////////////////////////////////////////////////////////////////////////////// // Click handling /////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java b/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java new file mode 100644 index 000000000000..e9708b75de27 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java @@ -0,0 +1,51 @@ +/* + * 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; + +import java.time.LocalDateTime; + +/** + * This generates the standard system variables for time. + */ +public class TimeVariables { + /** + * This class populates all time variables in the system + * @param context + */ + public void updateTime(RemoteContext context) { + LocalDateTime dateTime = LocalDateTime.now(); + // This define the time in the format + // seconds run from Midnight=0 quantized to seconds hour 0..3599 + // minutes run from Midnight=0 quantized to minutes 0..1439 + // hours run from Midnight=0 quantized to Hours 0-23 + // CONTINUOUS_SEC is seconds from midnight looping every hour 0-3600 + // CONTINUOUS_SEC is accurate to milliseconds due to float precession + int month = dateTime.getDayOfMonth(); + int hour = dateTime.getHour(); + int minute = dateTime.getMinute(); + int seconds = dateTime.getSecond(); + int currentMinute = hour * 60 + minute; + int currentSeconds = minute * 60 + seconds; + float sec = currentSeconds + dateTime.getNano() * 1E-9f; + + context.loadFloat(RemoteContext.ID_CONTINUOUS_SEC, sec); + context.loadFloat(RemoteContext.ID_TIME_IN_SEC, currentSeconds); + context.loadFloat(RemoteContext.ID_TIME_IN_MIN, currentMinute); + context.loadFloat(RemoteContext.ID_TIME_IN_HR, hour); + context.loadFloat(RemoteContext.ID_CALENDAR_MONTH, month); + + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/VariableSupport.java b/core/java/com/android/internal/widget/remotecompose/core/VariableSupport.java new file mode 100644 index 000000000000..d59b1bc65256 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/VariableSupport.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core; + +/** + * Interface for operators that interact with variables + * Threw this they register to listen to particular variables + * and are notified when they change + */ +public interface VariableSupport { + /** + * Call to allow an operator to register interest in variables. + * Typically they call context.listensTo(id, this) + * @param context + */ + void registerListening(RemoteContext context); + + /** + * Called to be notified that the variables you are interested have changed. + * @param context + */ + void updateVariables(RemoteContext context); +} 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 76b714443990..f1863225b766 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 @@ -51,11 +51,12 @@ public class BitmapData implements Operation { @Override public String toString() { - return "BITMAP DATA $imageId"; + return "BITMAP DATA " + mImageId; } public static class Companion implements CompanionOperation { - private Companion() {} + private Companion() { + } @Override public String name() { 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 8d4a787148ef..e6d5fe7043fd 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 @@ -24,6 +24,11 @@ import com.android.internal.widget.remotecompose.core.WireBuffer; import java.util.List; +/** + * Defines a path that clips a the subsequent drawing commands + * Use MatrixSave and MatrixRestore commands to remove clip + * TODO allow id 0 to mean null? + */ public class ClipPath extends PaintOperation { public static final Companion COMPANION = new Companion(); int mId; @@ -93,5 +98,4 @@ public class ClipPath extends PaintOperation { public void paint(PaintContext context) { context.clipPath(mId, mRegionOp); } -} - +}
\ No newline at end of file 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 803618a91737..613ecebf9100 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 @@ -15,88 +15,36 @@ */ package com.android.internal.widget.remotecompose.core.operations; -import com.android.internal.widget.remotecompose.core.CompanionOperation; 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.PaintOperation; -import com.android.internal.widget.remotecompose.core.WireBuffer; -import java.util.List; - -public class ClipRect extends PaintOperation { - public static final Companion COMPANION = new Companion(); - float mLeft; - float mTop; - float mRight; - float mBottom; +/** + * Support clip with a rectangle + */ +public class ClipRect extends DrawBase4 { + public static final Companion COMPANION = + new Companion(Operations.CLIP_RECT) { + @Override + public Operation construct(float x1, + float y1, + float x2, + float y2) { + return new ClipRect(x1, y1, x2, y2); + } + }; public ClipRect( float left, float top, float right, float bottom) { - mLeft = left; - mTop = top; - mRight = right; - mBottom = bottom; - - } - - @Override - public void write(WireBuffer buffer) { - COMPANION.apply(buffer, mLeft, mTop, mRight, mBottom); - } - - @Override - public String toString() { - return "ClipRect " + mLeft + " " + mTop - + " " + mRight + " " + mBottom + ";"; - } - - public static class Companion implements CompanionOperation { - private Companion() { - } - - @Override - public void read(WireBuffer buffer, List<Operation> operations) { - float sLeft = buffer.readFloat(); - float srcTop = buffer.readFloat(); - float srcRight = buffer.readFloat(); - float srcBottom = buffer.readFloat(); - - ClipRect op = new ClipRect(sLeft, srcTop, srcRight, srcBottom); - operations.add(op); - } - - @Override - public String name() { - return "ClipRect"; - } - - @Override - public int id() { - return Operations.CLIP_RECT; - } - - public void apply(WireBuffer buffer, - float left, - float top, - float right, - float bottom) { - buffer.start(Operations.CLIP_RECT); - buffer.writeFloat(left); - buffer.writeFloat(top); - buffer.writeFloat(right); - buffer.writeFloat(bottom); - } + super(left, top, right, bottom); + mName = "ClipRect"; } @Override public void paint(PaintContext context) { - context.clipRect(mLeft, - mTop, - mRight, - mBottom); + context.clipRect(mX1, mY1, mX2, mY2); } } 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 new file mode 100644 index 000000000000..7d28cea35850 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java @@ -0,0 +1,242 @@ +/* + * 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; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +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 java.util.List; + +/** + * Operation to Colors + * Color modes + * mMode = 0 two colors and a tween + * mMode = 1 color1 is a colorID. + * mMode = 2 color2 is a colorID. + * mMode = 3 color1 & color2 are ids + * mMode = 4 H S V mode + */ +public class ColorExpression implements Operation, VariableSupport { + public int mId; + int mMode; + public int mColor1; + public int mColor2; + public float mTween = 0.0f; + + + public float mHue = 0; // only in Mode 4 + public float mSat = 0; + public float mValue = 0; + public float mOutHue = 0; // only in Mode 4 + public float mOutSat = 0; + public float mOutValue = 0; + public int mAlpha = 0xFF; // only used in hsv mode + + public float mOutTween = 0.0f; + public int mOutColor1; + public int mOutColor2; + public static final Companion COMPANION = new Companion(); + public static final int HSV_MODE = 4; + public ColorExpression(int id, float hue, float sat, float value) { + mMode = HSV_MODE; + mAlpha = 0xFF; + mOutHue = mHue = hue; + mOutSat = mSat = sat; + mOutValue = mValue = value; + mColor1 = Float.floatToRawIntBits(hue); + mColor2 = Float.floatToRawIntBits(sat); + mTween = value; + } + public ColorExpression(int id, int alpha, float hue, float sat, float value) { + mMode = HSV_MODE; + mAlpha = alpha; + mOutHue = mHue = hue; + mOutSat = mSat = sat; + mOutValue = mValue = value; + mColor1 = Float.floatToRawIntBits(hue); + mColor2 = Float.floatToRawIntBits(sat); + mTween = value; + } + + public ColorExpression(int id, int mode, int color1, int color2, float tween) { + this.mId = id; + this.mMode = mode & 0xFF; + this.mAlpha = (mode >> 16) & 0xFF; + if (mMode == HSV_MODE) { + mOutHue = mHue = Float.intBitsToFloat(color1); + mOutSat = mSat = Float.intBitsToFloat(color2); + mOutValue = mValue = tween; + } + this.mColor1 = color1; + this.mColor2 = color2; + this.mTween = tween; + this.mOutTween = tween; + this.mOutColor1 = color1; + this.mOutColor2 = color2; + + } + + @Override + public void updateVariables(RemoteContext context) { + if (mMode == 4) { + if (Float.isNaN(mHue)) { + mOutHue = context.getFloat(Utils.idFromNan(mHue)); + } + if (Float.isNaN(mSat)) { + mOutSat = context.getFloat(Utils.idFromNan(mSat)); + } + if (Float.isNaN(mValue)) { + mOutValue = context.getFloat(Utils.idFromNan(mValue)); + } + } + if (Float.isNaN(mTween)) { + mOutTween = context.getFloat(Utils.idFromNan(mTween)); + } + if ((mMode & 1) == 1) { + mOutColor1 = context.getColor(mColor1); + } + if ((mMode & 2) == 2) { + mOutColor2 = context.getColor(mColor2); + } + } + + + @Override + public void registerListening(RemoteContext context) { + if (mMode == 4) { + if (Float.isNaN(mHue)) { + context.listensTo(Utils.idFromNan(mHue), this); + } + if (Float.isNaN(mSat)) { + context.listensTo(Utils.idFromNan(mSat), this); + } + if (Float.isNaN(mValue)) { + context.listensTo(Utils.idFromNan(mValue), this); + } + return; + } + if (Float.isNaN(mTween)) { + context.listensTo(Utils.idFromNan(mTween), this); + } + if ((mMode & 1) == 1) { + context.listensTo(mColor1, this); + } + if ((mMode & 2) == 2) { + context.listensTo(mColor2, this); + } + } + + @Override + public void apply(RemoteContext context) { + if (mMode == 4) { + context.loadColor(mId, (mAlpha << 24) + | (0xFFFFFF & Utils.hsvToRgb(mOutHue, mOutSat, mOutValue))); + return; + } + if (mOutTween == 0.0) { + context.loadColor(mId, mColor1); + } else { + if ((mMode & 1) == 1) { + mOutColor1 = context.getColor(mColor1); + } + if ((mMode & 2) == 2) { + mOutColor2 = context.getColor(mColor2); + } + + context.loadColor(mId, + Utils.interpolateColor(mOutColor1, mOutColor2, mOutTween)); + } + + } + + @Override + public void write(WireBuffer buffer) { + int mode = mMode | (mAlpha << 16); + COMPANION.apply(buffer, mId, mode, mColor1, mColor2, mTween); + } + + @Override + public String toString() { + if (mMode == 4) { + return "ColorExpression[" + mId + "] = hsv (" + Utils.floatToString(mHue) + + ", " + Utils.floatToString(mSat) + + ", " + Utils.floatToString(mValue) + ")"; + } + + String c1 = (mMode & 1) == 1 ? "[" + mColor1 + "]" : Utils.colorInt(mColor1); + String c2 = (mMode & 2) == 2 ? "[" + mColor2 + "]" : Utils.colorInt(mColor2); + return "ColorExpression[" + mId + "] = tween(" + c1 + + ", " + c2 + ", " + + Utils.floatToString(mTween) + ")"; + } + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public String name() { + return "ColorExpression"; + } + + @Override + public int id() { + return Operations.COLOR_EXPRESSIONS; + } + + /** + * Call to write a ColorExpression object on the buffer + * @param buffer + * @param id of the ColorExpression object + * @param mode if colors are id or actual values + * @param color1 + * @param color2 + * @param tween + */ + public void apply(WireBuffer buffer, + int id, int mode, + int color1, int color2, float tween) { + buffer.start(Operations.COLOR_EXPRESSIONS); + buffer.writeInt(id); + buffer.writeInt(mode); + buffer.writeInt(color1); + buffer.writeInt(color2); + buffer.writeFloat(tween); + + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + int id = buffer.readInt(); + int mode = buffer.readInt(); + int color1 = buffer.readInt(); + int color2 = buffer.readInt(); + float tween = buffer.readFloat(); + + operations.add(new ColorExpression(id, mode, color1, color2, tween)); + } + } + + @Override + public String deepToString(String indent) { + return indent + toString(); + } + +} 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 e829975cd39b..c1768647bde6 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 @@ -15,107 +15,36 @@ */ package com.android.internal.widget.remotecompose.core.operations; -import com.android.internal.widget.remotecompose.core.CompanionOperation; 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.PaintOperation; -import com.android.internal.widget.remotecompose.core.WireBuffer; -import java.util.List; - -public class DrawArc extends PaintOperation { - public static final Companion COMPANION = new Companion(); - float mLeft; - float mTop; - float mRight; - float mBottom; - float mStartAngle; - float mSweepAngle; - - public DrawArc( - float left, - float top, - float right, - float bottom, - float startAngle, - float sweepAngle) { - mLeft = left; - mTop = top; - mRight = right; - mBottom = bottom; - mStartAngle = startAngle; - mSweepAngle = sweepAngle; - } - - @Override - public void write(WireBuffer buffer) { - COMPANION.apply(buffer, mLeft, - mTop, - mRight, - mBottom, - mStartAngle, - mSweepAngle); - } - - @Override - public String toString() { - return "DrawArc " + mLeft + " " + mTop - + " " + mRight + " " + mBottom + " " - + "- " + mStartAngle + " " + mSweepAngle + ";"; - } - - public static class Companion implements CompanionOperation { - private Companion() { - } - - @Override - public void read(WireBuffer buffer, List<Operation> operations) { - float sLeft = buffer.readFloat(); - float srcTop = buffer.readFloat(); - float srcRight = buffer.readFloat(); - float srcBottom = buffer.readFloat(); - float mStartAngle = buffer.readFloat(); - float mSweepAngle = buffer.readFloat(); - DrawArc op = new DrawArc(sLeft, srcTop, srcRight, srcBottom, - mStartAngle, mSweepAngle); - operations.add(op); - } - - @Override - public String name() { - return "DrawArc"; - } - - @Override - public int id() { - return Operations.DRAW_ARC; - } - - public void apply(WireBuffer buffer, - float left, - float top, - float right, - float bottom, - float startAngle, - float sweepAngle) { - buffer.start(Operations.DRAW_ARC); - buffer.writeFloat(left); - buffer.writeFloat(top); - buffer.writeFloat(right); - buffer.writeFloat(bottom); - buffer.writeFloat(startAngle); - buffer.writeFloat(sweepAngle); - } +public class DrawArc extends DrawBase6 { + public static final Companion COMPANION = + new Companion(Operations.DRAW_ARC) { + @Override + public Operation construct(float v1, + float v2, + float v3, + float v4, + float v5, + float v6) { + return new DrawArc(v1, v2, v3, v4, v5, v6); + } + }; + + public DrawArc(float v1, + float v2, + float v3, + float v4, + float v5, + float v6) { + super(v1, v2, v3, v4, v5, v6); + mName = "DrawArc"; } @Override public void paint(PaintContext context) { - context.drawArc(mLeft, - mTop, - mRight, - mBottom, - mStartAngle, - mSweepAngle); + context.drawArc(mV1, mV2, mV3, mV4, mV5, mV6); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java new file mode 100644 index 000000000000..0963c1337b70 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations; + +import static com.android.internal.widget.remotecompose.core.operations.Utils.floatToString; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintOperation; +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 java.util.List; + +/** + * Base class for commands that take 3 float + */ +public abstract class DrawBase2 extends PaintOperation + implements VariableSupport { + public static final Companion COMPANION = + new Companion(Operations.DRAW_CIRCLE) { + @Override + public Operation construct(float x1, float y1) { + // subclass should return new DrawX(x1, y1); + return null; + } + }; + protected String mName = "DrawRectBase"; + float mV1; + float mV2; + float mValue1; + float mValue2; + + public DrawBase2(float v1, float v2) { + mValue1 = v1; + mValue2 = v2; + mV1 = v1; + mV2 = v2; + } + + @Override + public void updateVariables(RemoteContext context) { + mV1 = (Float.isNaN(mValue1)) + ? context.getFloat(Utils.idFromNan(mValue1)) : mValue1; + mV2 = (Float.isNaN(mValue2)) + ? context.getFloat(Utils.idFromNan(mValue2)) : mValue2; + } + + @Override + public void registerListening(RemoteContext context) { + if (Float.isNaN(mValue1)) { + context.listensTo(Utils.idFromNan(mValue1), this); + } + if (Float.isNaN(mValue2)) { + context.listensTo(Utils.idFromNan(mValue2), this); + } + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mV1, mV2); + } + + @Override + public String toString() { + return mName + " " + floatToString(mV1) + " " + floatToString(mV2); + } + + public static class Companion implements CompanionOperation { + public final int OP_CODE; + + protected Companion(int code) { + OP_CODE = code; + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + float v1 = buffer.readFloat(); + float v2 = buffer.readFloat(); + + Operation op = construct(v1, v2); + operations.add(op); + } + + /** + * Override to construct a 2 float value operation + * @param x1 + * @param y1 + * @return + */ + public Operation construct(float x1, float y1) { + return null; + } + + @Override + public String name() { + return "DrawRect"; + } + + @Override + public int id() { + return OP_CODE; + } + + /** + * Writes out the operation to the buffer + * @param buffer + * @param x1 + * @param y1 + */ + public void apply(WireBuffer buffer, + float x1, + float y1) { + buffer.start(OP_CODE); + buffer.writeFloat(x1); + buffer.writeFloat(y1); + } + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java new file mode 100644 index 000000000000..56b2f1f7bb86 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations; + +import static com.android.internal.widget.remotecompose.core.operations.Utils.floatToString; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintOperation; +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 java.util.List; + +/** + * Base class for commands that take 3 float + */ +public abstract class DrawBase3 extends PaintOperation + implements VariableSupport { + public static final Companion COMPANION = + new Companion(Operations.DRAW_CIRCLE) { + @Override + public Operation construct(float x1, float y1, float x2) { + // subclass should return new DrawX(x1, y1, x2, y2); + return null; + } + }; + protected String mName = "DrawRectBase"; + float mV1; + float mV2; + float mV3; + float mValue1; + float mValue2; + float mValue3; + + public DrawBase3( + float v1, + float v2, + float v3) { + mValue1 = v1; + mValue2 = v2; + mValue3 = v3; + + mV1 = v1; + mV2 = v2; + mV3 = v3; + } + + @Override + public void updateVariables(RemoteContext context) { + mV1 = (Float.isNaN(mValue1)) + ? context.getFloat(Utils.idFromNan(mValue1)) : mValue1; + mV2 = (Float.isNaN(mValue2)) + ? context.getFloat(Utils.idFromNan(mValue2)) : mValue2; + mV3 = (Float.isNaN(mValue3)) + ? context.getFloat(Utils.idFromNan(mValue3)) : mValue3; + } + + @Override + public void registerListening(RemoteContext context) { + if (Float.isNaN(mValue1)) { + context.listensTo(Utils.idFromNan(mValue1), this); + } + if (Float.isNaN(mValue2)) { + context.listensTo(Utils.idFromNan(mValue2), this); + } + if (Float.isNaN(mValue3)) { + context.listensTo(Utils.idFromNan(mValue3), this); + } + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mV1, mV2, mV3); + } + + @Override + public String toString() { + return mName + " " + floatToString(mV1) + " " + floatToString(mV2) + + " " + floatToString(mV3); + } + + public static class Companion implements CompanionOperation { + public final int OP_CODE; + + protected Companion(int code) { + OP_CODE = code; + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + float v1 = buffer.readFloat(); + float v2 = buffer.readFloat(); + float v3 = buffer.readFloat(); + + Operation op = construct(v1, v2, v3); + operations.add(op); + } + + /** + * Construct and Operation from the 3 variables. + * This must be overridden by subclasses + * @param x1 + * @param y1 + * @param x2 + * @return + */ + public Operation construct(float x1, + float y1, + float x2) { + return null; + } + + @Override + public String name() { + return "DrawRect"; + } + + @Override + public int id() { + return OP_CODE; + } + + /** + * Writes out the operation to the buffer + * @param buffer + * @param x1 + * @param y1 + * @param x2 + */ + public void apply(WireBuffer buffer, + float x1, + float y1, + float x2) { + buffer.start(OP_CODE); + buffer.writeFloat(x1); + buffer.writeFloat(y1); + buffer.writeFloat(x2); + + } + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java new file mode 100644 index 000000000000..ec35a160079c --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations; + +import static com.android.internal.widget.remotecompose.core.operations.Utils.floatToString; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintOperation; +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 java.util.List; + +/** + * Base class for draw commands that take 4 floats + */ +public abstract class DrawBase4 extends PaintOperation + implements VariableSupport { + public static final Companion COMPANION = + new Companion(Operations.DRAW_RECT) { + @Override + public Operation construct(float x1, float y1, float x2, float y2) { + // return new DrawRectBase(x1, y1, x2, y2); + return null; + } + }; + protected String mName = "DrawRectBase"; + float mX1; + float mY1; + float mX2; + float mY2; + float mX1Value; + float mY1Value; + float mX2Value; + float mY2Value; + + public DrawBase4( + float x1, + float y1, + float x2, + float y2) { + mX1Value = x1; + mY1Value = y1; + mX2Value = x2; + mY2Value = y2; + + mX1 = x1; + mY1 = y1; + mX2 = x2; + mY2 = y2; + } + + @Override + public void updateVariables(RemoteContext context) { + mX1 = (Float.isNaN(mX1Value)) + ? context.getFloat(Utils.idFromNan(mX1Value)) : mX1Value; + mY1 = (Float.isNaN(mY1Value)) + ? context.getFloat(Utils.idFromNan(mY1Value)) : mY1Value; + mX2 = (Float.isNaN(mX2Value)) + ? context.getFloat(Utils.idFromNan(mX2Value)) : mX2Value; + mY2 = (Float.isNaN(mY2Value)) + ? context.getFloat(Utils.idFromNan(mY2Value)) : mY2Value; + } + + @Override + public void registerListening(RemoteContext context) { + if (Float.isNaN(mX1Value)) { + context.listensTo(Utils.idFromNan(mX1Value), this); + } + if (Float.isNaN(mY1Value)) { + context.listensTo(Utils.idFromNan(mY1Value), this); + } + if (Float.isNaN(mX2Value)) { + context.listensTo(Utils.idFromNan(mX2Value), this); + } + if (Float.isNaN(mY2Value)) { + context.listensTo(Utils.idFromNan(mY2Value), this); + } + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mX1, mY1, mX2, mY2); + } + + @Override + public String toString() { + return mName + " " + floatToString(mX1Value, mX1) + " " + floatToString(mY1Value, mY1) + + " " + floatToString(mX2Value, mX2) + " " + floatToString(mY2Value, mY2); + } + + public static class Companion implements CompanionOperation { + public final int OP_CODE; + + protected Companion(int code) { + OP_CODE = code; + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + float sLeft = buffer.readFloat(); + float srcTop = buffer.readFloat(); + float srcRight = buffer.readFloat(); + float srcBottom = buffer.readFloat(); + + Operation op = construct(sLeft, srcTop, srcRight, srcBottom); + operations.add(op); + } + + /** + * Construct and Operation from the 3 variables. + * @param x1 + * @param y1 + * @param x2 + * @param y2 + * @return + */ + public Operation construct(float x1, + float y1, + float x2, + float y2) { + return null; + } + + @Override + public String name() { + return "DrawRect"; + } + + @Override + public int id() { + return OP_CODE; + } + + /** + * Writes out the operation to the buffer + * @param buffer + * @param x1 + * @param y1 + * @param x2 + * @param y2 + */ + public void apply(WireBuffer buffer, + float x1, + float y1, + float x2, + float y2) { + buffer.start(OP_CODE); + buffer.writeFloat(x1); + buffer.writeFloat(y1); + buffer.writeFloat(x2); + buffer.writeFloat(y2); + } + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java new file mode 100644 index 000000000000..2f4335e7f412 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations; + +import static com.android.internal.widget.remotecompose.core.operations.Utils.floatToString; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintOperation; +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 java.util.List; + +/** + * Base class for draw commands the take 6 floats + */ +public abstract class DrawBase6 extends PaintOperation + implements VariableSupport { + public static final Companion COMPANION = + new Companion(Operations.DRAW_RECT) { + public Operation construct(float x1, float y1, float x2, float y2) { + // return new DrawRectBase(x1, y1, x2, y2); + return null; + } + }; + protected String mName = "DrawRectBase"; + float mV1; + float mV2; + float mV3; + float mV4; + float mV5; + float mV6; + float mValue1; + float mValue2; + float mValue3; + float mValue4; + float mValue5; + float mValue6; + + public DrawBase6( + float v1, + float v2, + float v3, + float v4, + float v5, + float v6) { + mValue1 = v1; + mValue2 = v2; + mValue3 = v3; + mValue4 = v4; + mValue5 = v5; + mValue6 = v6; + + mV1 = v1; + mV2 = v2; + mV3 = v3; + mV4 = v4; + mV5 = v5; + mV6 = v6; + } + + @Override + public void updateVariables(RemoteContext context) { + mV1 = (Float.isNaN(mValue1)) + ? context.getFloat(Utils.idFromNan(mValue1)) : mValue1; + mV2 = (Float.isNaN(mValue2)) + ? context.getFloat(Utils.idFromNan(mValue2)) : mValue2; + mV3 = (Float.isNaN(mValue3)) + ? context.getFloat(Utils.idFromNan(mValue3)) : mValue3; + mV4 = (Float.isNaN(mValue4)) + ? context.getFloat(Utils.idFromNan(mValue4)) : mValue4; + mV5 = (Float.isNaN(mValue5)) + ? context.getFloat(Utils.idFromNan(mValue5)) : mValue5; + mV6 = (Float.isNaN(mValue6)) + ? context.getFloat(Utils.idFromNan(mValue6)) : mValue6; + } + + @Override + public void registerListening(RemoteContext context) { + if (Float.isNaN(mValue1)) { + context.listensTo(Utils.idFromNan(mValue1), this); + } + if (Float.isNaN(mValue2)) { + context.listensTo(Utils.idFromNan(mValue2), this); + } + if (Float.isNaN(mValue3)) { + context.listensTo(Utils.idFromNan(mValue3), this); + } + if (Float.isNaN(mValue4)) { + context.listensTo(Utils.idFromNan(mValue4), this); + } + if (Float.isNaN(mValue5)) { + context.listensTo(Utils.idFromNan(mValue5), this); + } + if (Float.isNaN(mValue6)) { + context.listensTo(Utils.idFromNan(mValue6), this); + } + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mV1, mV2, mV3, mV4, mV5, mV6); + } + + @Override + public String toString() { + return mName + " " + floatToString(mV1) + " " + floatToString(mV2) + + " " + floatToString(mV3) + " " + floatToString(mV4); + } + + public static class Companion implements CompanionOperation { + public final int OP_CODE; + + protected Companion(int code) { + OP_CODE = code; + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + float sv1 = buffer.readFloat(); + float sv2 = buffer.readFloat(); + float sv3 = buffer.readFloat(); + float sv4 = buffer.readFloat(); + float sv5 = buffer.readFloat(); + float sv6 = buffer.readFloat(); + + Operation op = construct(sv1, sv2, sv3, sv4, sv5, sv6); + operations.add(op); + } + + /** + * writes out a the operation to the buffer. + * @param v1 + * @param v2 + * @param v3 + * @param v4 + * @param v5 + * @param v6 + * @return + */ + public Operation construct(float v1, + float v2, + float v3, + float v4, + float v5, + float v6) { + return null; + } + + @Override + public String name() { + return "DrawRect"; + } + + @Override + public int id() { + return OP_CODE; + } + + /** + * Writes out the operation to the buffer + * @param buffer + * @param v1 + * @param v2 + * @param v3 + * @param v4 + * @param v5 + * @param v6 + */ + public void apply(WireBuffer buffer, + float v1, + float v2, + float v3, + float v4, + float v5, + float v6) { + buffer.start(OP_CODE); + buffer.writeFloat(v1); + buffer.writeFloat(v2); + buffer.writeFloat(v3); + buffer.writeFloat(v4); + buffer.writeFloat(v5); + buffer.writeFloat(v6); + } + } +} 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 2e971f533ed2..ca40d12fd3f9 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 @@ -20,16 +20,22 @@ 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.PaintOperation; +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 java.util.List; -public class DrawBitmap extends PaintOperation { +public class DrawBitmap extends PaintOperation implements VariableSupport { public static final Companion COMPANION = new Companion(); float mLeft; float mTop; float mRight; float mBottom; + float mOutputLeft; + float mOutputTop; + float mOutputRight; + float mOutputBottom; int mId; int mDescriptionId = 0; @@ -49,6 +55,34 @@ public class DrawBitmap extends PaintOperation { } @Override + public void updateVariables(RemoteContext context) { + mOutputLeft = (Float.isNaN(mLeft)) + ? context.getFloat(Utils.idFromNan(mLeft)) : mLeft; + mOutputTop = (Float.isNaN(mTop)) + ? context.getFloat(Utils.idFromNan(mTop)) : mTop; + mOutputRight = (Float.isNaN(mRight)) + ? context.getFloat(Utils.idFromNan(mRight)) : mRight; + mOutputBottom = (Float.isNaN(mBottom)) + ? context.getFloat(Utils.idFromNan(mBottom)) : mBottom; + } + + @Override + public void registerListening(RemoteContext context) { + if (Float.isNaN(mLeft)) { + context.listensTo(Utils.idFromNan(mLeft), this); + } + if (Float.isNaN(mTop)) { + context.listensTo(Utils.idFromNan(mTop), this); + } + if (Float.isNaN(mRight)) { + context.listensTo(Utils.idFromNan(mRight), this); + } + if (Float.isNaN(mBottom)) { + context.listensTo(Utils.idFromNan(mBottom), this); + } + } + + @Override public void write(WireBuffer buffer) { COMPANION.apply(buffer, mId, mLeft, mTop, mRight, mBottom, mDescriptionId); } @@ -105,9 +139,9 @@ public class DrawBitmap extends PaintOperation { @Override public void paint(PaintContext context) { - context.drawBitmap(mId, mLeft, - mTop, - mRight, - mBottom); + context.drawBitmap(mId, mOutputLeft, + mOutputTop, + mOutputRight, + mOutputBottom); } } 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 9ce754da1b1b..3a22e4f72720 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 @@ -1,89 +1,31 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ package com.android.internal.widget.remotecompose.core.operations; -import com.android.internal.widget.remotecompose.core.CompanionOperation; 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.PaintOperation; -import com.android.internal.widget.remotecompose.core.WireBuffer; -import java.util.List; - -public class DrawCircle extends PaintOperation { - public static final Companion COMPANION = new Companion(); - float mCenterX; - float mCenterY; - float mRadius; - - public DrawCircle(float centerX, float centerY, float radius) { - mCenterX = centerX; - mCenterY = centerY; - mRadius = radius; - } - - @Override - public void write(WireBuffer buffer) { - COMPANION.apply(buffer, mCenterX, - mCenterY, - mRadius); - } - - @Override - public String toString() { - return ""; - } - - public static class Companion implements CompanionOperation { - private Companion() { - } - - @Override - public void read(WireBuffer buffer, List<Operation> operations) { - float centerX = buffer.readFloat(); - float centerY = buffer.readFloat(); - float radius = buffer.readFloat(); - - DrawCircle op = new DrawCircle(centerX, centerY, radius); - operations.add(op); - } - - @Override - public String name() { - return ""; - } - - @Override - public int id() { - return 0; - } - - public void apply(WireBuffer buffer, float centerX, float centerY, float radius) { - buffer.start(Operations.DRAW_CIRCLE); - buffer.writeFloat(centerX); - buffer.writeFloat(centerY); - buffer.writeFloat(radius); - } +public class DrawCircle extends DrawBase3 { + public static final Companion COMPANION = + new Companion(Operations.DRAW_CIRCLE) { + @Override + public Operation construct(float x1, + float y1, + float x2 + ) { + return new DrawCircle(x1, y1, x2); + } + }; + + public DrawCircle( + float left, + float top, + float right) { + super(left, top, right); + mName = "DrawCircle"; } @Override public void paint(PaintContext context) { - context.drawCircle(mCenterX, - mCenterY, - mRadius); + context.drawCircle(mV1, mV2, mV3); } } 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 c7a8315a2274..c70c6eaa449d 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 @@ -15,83 +15,28 @@ */ package com.android.internal.widget.remotecompose.core.operations; -import com.android.internal.widget.remotecompose.core.CompanionOperation; 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.PaintOperation; -import com.android.internal.widget.remotecompose.core.WireBuffer; - -import java.util.List; - -public class DrawLine extends PaintOperation { - public static final Companion COMPANION = new Companion(); - float mX1; - float mY1; - float mX2; - float mY2; - - public DrawLine( - float x1, - float y1, - float x2, - float y2) { - mX1 = x1; - mY1 = y1; - mX2 = x2; - mY2 = y2; - } - - @Override - public void write(WireBuffer buffer) { - COMPANION.apply(buffer, mX1, - mY1, - mX2, - mY2); - } - - @Override - public String toString() { - return "DrawArc " + mX1 + " " + mY1 - + " " + mX2 + " " + mY2 + ";"; - } - - public static class Companion implements CompanionOperation { - private Companion() { - } - - @Override - public void read(WireBuffer buffer, List<Operation> operations) { - float x1 = buffer.readFloat(); - float y1 = buffer.readFloat(); - float x2 = buffer.readFloat(); - float y2 = buffer.readFloat(); - - DrawLine op = new DrawLine(x1, y1, x2, y2); - operations.add(op); - } - - @Override - public String name() { - return "DrawLine"; - } +public class DrawLine extends DrawBase4 { + public static final Companion COMPANION = new Companion(Operations.DRAW_LINE) { @Override - public int id() { - return Operations.DRAW_LINE; + public Operation construct(float x1, + float y1, + float x2, + float y2) { + return new DrawLine(x1, y1, x2, y2); } + }; - public void apply(WireBuffer buffer, - float x1, - float y1, - float x2, - float y2) { - buffer.start(Operations.DRAW_LINE); - buffer.writeFloat(x1); - buffer.writeFloat(y1); - buffer.writeFloat(x2); - buffer.writeFloat(y2); - } + public DrawLine( + float left, + float top, + float right, + float bottom) { + super(left, top, right, bottom); + mName = "DrawLine"; } @Override 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 714375335cb2..ba1799422e80 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 @@ -15,88 +15,33 @@ */ package com.android.internal.widget.remotecompose.core.operations; -import com.android.internal.widget.remotecompose.core.CompanionOperation; 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.PaintOperation; -import com.android.internal.widget.remotecompose.core.WireBuffer; - -import java.util.List; - -public class DrawOval extends PaintOperation { - public static final Companion COMPANION = new Companion(); - float mLeft; - float mTop; - float mRight; - float mBottom; +public class DrawOval extends DrawBase4 { + public static final Companion COMPANION = + new Companion(Operations.DRAW_OVAL) { + @Override + public Operation construct(float x1, + float y1, + float x2, + float y2) { + return new DrawOval(x1, y1, x2, y2); + } + }; public DrawOval( float left, float top, float right, float bottom) { - mLeft = left; - mTop = top; - mRight = right; - mBottom = bottom; - } - - @Override - public void write(WireBuffer buffer) { - COMPANION.apply(buffer, mLeft, mTop, mRight, mBottom); - } - - @Override - public String toString() { - return "DrawOval " + mLeft + " " + mTop - + " " + mRight + " " + mBottom + ";"; - } - - public static class Companion implements CompanionOperation { - private Companion() { - } - - @Override - public void read(WireBuffer buffer, List<Operation> operations) { - float sLeft = buffer.readFloat(); - float srcTop = buffer.readFloat(); - float srcRight = buffer.readFloat(); - float srcBottom = buffer.readFloat(); - - DrawOval op = new DrawOval(sLeft, srcTop, srcRight, srcBottom); - operations.add(op); - } - - @Override - public String name() { - return "DrawOval"; - } - - @Override - public int id() { - return Operations.DRAW_OVAL; - } - - public void apply(WireBuffer buffer, - float left, - float top, - float right, - float bottom) { - buffer.start(Operations.DRAW_OVAL); - buffer.writeFloat(left); - buffer.writeFloat(top); - buffer.writeFloat(right); - buffer.writeFloat(bottom); - } + super(left, top, right, bottom); + mName = "DrawOval"; } @Override public void paint(PaintContext context) { - context.drawOval(mLeft, - mTop, - mRight, - mBottom); + context.drawOval(mX1, mY1, mX2, mY2); } } 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 7b8a9e95d9cb..6dbc5a628c98 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 @@ -41,7 +41,7 @@ public class DrawPath extends PaintOperation { @Override public String toString() { - return "DrawPath " + ";"; + return "DrawPath " + "[" + mId + "]" + ", " + mStart + ", " + mEnd; } public static class Companion implements CompanionOperation { 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 4775241faa6f..633aed4a4dbe 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 @@ -15,88 +15,37 @@ */ package com.android.internal.widget.remotecompose.core.operations; -import com.android.internal.widget.remotecompose.core.CompanionOperation; 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.PaintOperation; -import com.android.internal.widget.remotecompose.core.WireBuffer; -import java.util.List; - -public class DrawRect extends PaintOperation { - public static final Companion COMPANION = new Companion(); - float mLeft; - float mTop; - float mRight; - float mBottom; +/** + * Draw a Rectangle + */ +public class DrawRect extends DrawBase4 { + public static final Companion COMPANION = + new Companion(Operations.DRAW_RECT) { + @Override + public Operation construct(float x1, + float y1, + float x2, + float y2) { + return new DrawRect(x1, y1, x2, y2); + } + }; public DrawRect( float left, float top, float right, float bottom) { - mLeft = left; - mTop = top; - mRight = right; - mBottom = bottom; - } - - @Override - public void write(WireBuffer buffer) { - COMPANION.apply(buffer, mLeft, mTop, mRight, mBottom); - } - - @Override - public String toString() { - return "DrawRect " + mLeft + " " + mTop - + " " + mRight + " " + mBottom + ";"; - } - - public static class Companion implements CompanionOperation { - private Companion() { - } - - @Override - public void read(WireBuffer buffer, List<Operation> operations) { - float sLeft = buffer.readFloat(); - float srcTop = buffer.readFloat(); - float srcRight = buffer.readFloat(); - float srcBottom = buffer.readFloat(); - - DrawRect op = new DrawRect(sLeft, srcTop, srcRight, srcBottom); - operations.add(op); - } - - @Override - public String name() { - return "DrawRect"; - } - - @Override - public int id() { - return Operations.DRAW_RECT; - } - - public void apply(WireBuffer buffer, - float left, - float top, - float right, - float bottom) { - buffer.start(Operations.DRAW_RECT); - buffer.writeFloat(left); - buffer.writeFloat(top); - buffer.writeFloat(right); - buffer.writeFloat(bottom); - } + super(left, top, right, bottom); + mName = "DrawRect"; } @Override public void paint(PaintContext context) { - context.drawRect(mLeft, - mTop, - mRight, - mBottom); + context.drawRect(mX1, mY1, mX2, mY2); } } 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 8da16e768b7f..b9d0a6728b95 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 @@ -15,104 +15,40 @@ */ package com.android.internal.widget.remotecompose.core.operations; -import com.android.internal.widget.remotecompose.core.CompanionOperation; 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.PaintOperation; -import com.android.internal.widget.remotecompose.core.WireBuffer; -import java.util.List; - -public class DrawRoundRect extends PaintOperation { - public static final Companion COMPANION = new Companion(); - float mLeft; - float mTop; - float mRight; - float mBottom; - float mRadiusX; - float mRadiusY; - - public DrawRoundRect( - float left, - float top, - float right, - float bottom, - float radiusX, - float radiusY) { - mLeft = left; - mTop = top; - mRight = right; - mBottom = bottom; - mRadiusX = radiusX; - mRadiusY = radiusY; - } - - @Override - public void write(WireBuffer buffer) { - COMPANION.apply(buffer, mLeft, mTop, mRight, mBottom, mRadiusX, mRadiusY); - } - - @Override - public String toString() { - return "DrawRoundRect " + mLeft + " " + mTop - + " " + mRight + " " + mBottom - + " (" + mRadiusX + " " + mRadiusY + ");"; - } - - public static class Companion implements CompanionOperation { - private Companion() { - } - - @Override - public void read(WireBuffer buffer, List<Operation> operations) { - float sLeft = buffer.readFloat(); - float srcTop = buffer.readFloat(); - float srcRight = buffer.readFloat(); - float srcBottom = buffer.readFloat(); - float srcRadiusX = buffer.readFloat(); - float srcRadiusY = buffer.readFloat(); - - DrawRoundRect op = new DrawRoundRect(sLeft, srcTop, srcRight, - srcBottom, srcRadiusX, srcRadiusY); - operations.add(op); - } - - @Override - public String name() { - return "DrawOval"; - } - - @Override - public int id() { - return Operations.DRAW_ROUND_RECT; - } - - public void apply(WireBuffer buffer, - float left, - float top, - float right, - float bottom, - float radiusX, - float radiusY) { - buffer.start(Operations.DRAW_ROUND_RECT); - buffer.writeFloat(left); - buffer.writeFloat(top); - buffer.writeFloat(right); - buffer.writeFloat(bottom); - buffer.writeFloat(radiusX); - buffer.writeFloat(radiusY); - } +/** + * Draw a rounded rectangle + */ +public class DrawRoundRect extends DrawBase6 { + public static final Companion COMPANION = + new Companion(Operations.DRAW_ROUND_RECT) { + @Override + public Operation construct(float v1, + float v2, + float v3, + float v4, + float v5, + float v6) { + return new DrawRoundRect(v1, v2, v3, v4, v5, v6); + } + }; + + public DrawRoundRect(float v1, + float v2, + float v3, + float v4, + float v5, + float v6) { + super(v1, v2, v3, v4, v5, v6); + mName = "ClipRect"; } @Override public void paint(PaintContext context) { - context.drawRoundRect(mLeft, - mTop, - mRight, - mBottom, - mRadiusX, - mRadiusY + context.drawRoundRect(mV1, mV2, mV3, mV4, mV5, mV6 ); } 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 new file mode 100644 index 000000000000..f8f8afdf68cd --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +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.PaintOperation; +import com.android.internal.widget.remotecompose.core.WireBuffer; + +import java.util.List; + +/** + * Draw Text + */ +public class DrawText extends PaintOperation { + public static final Companion COMPANION = new Companion(); + int mTextID; + int mStart = 0; + int mEnd = 0; + int mContextStart = 0; + int mContextEnd = 0; + float mX = 0f; + float mY = 0f; + boolean mRtl = false; + + public DrawText(int textID, + int start, + int end, + int contextStart, + int contextEnd, + float x, + float y, + boolean rtl) { + mTextID = textID; + mStart = start; + mEnd = end; + mContextStart = contextStart; + mContextEnd = contextEnd; + mX = x; + mY = y; + mRtl = rtl; + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mTextID, mStart, mEnd, mContextStart, mContextEnd, mX, mY, mRtl); + + } + + @Override + public String toString() { + return "DrawTextRun [" + mTextID + "] " + mStart + ", " + mEnd + ", " + mX + ", " + mY; + } + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + int text = buffer.readInt(); + int start = buffer.readInt(); + int end = buffer.readInt(); + int contextStart = buffer.readInt(); + int contextEnd = buffer.readInt(); + float x = buffer.readFloat(); + float y = buffer.readFloat(); + boolean rtl = buffer.readBoolean(); + DrawText op = new DrawText(text, start, end, contextStart, contextEnd, x, y, rtl); + + operations.add(op); + } + + @Override + public String name() { + return ""; + } + + @Override + public int id() { + return 0; + } + + /** + * Writes out the operation to the buffer + * @param buffer + * @param textID + * @param start + * @param end + * @param contextStart + * @param contextEnd + * @param x + * @param y + * @param rtl + */ + public void apply(WireBuffer buffer, + int textID, + int start, + int end, + int contextStart, + int contextEnd, + float x, + float y, + boolean rtl) { + buffer.start(Operations.DRAW_TEXT_RUN); + buffer.writeInt(textID); + buffer.writeInt(start); + buffer.writeInt(end); + buffer.writeInt(contextStart); + buffer.writeInt(contextEnd); + buffer.writeFloat(x); + buffer.writeFloat(y); + buffer.writeBoolean(rtl); + } + } + + @Override + public void paint(PaintContext context) { + context.drawTextRun(mTextID, mStart, mEnd, mContextStart, mContextEnd, mX, mY, 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 new file mode 100644 index 000000000000..4f0641f34d84 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +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.PaintOperation; +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 java.util.List; + +/** + * Draw Text in Anchored to a point + */ +public class DrawTextAnchored extends PaintOperation implements VariableSupport { + public static final Companion COMPANION = new Companion(); + int mTextID; + float mX; + float mY; + float mPanX; + float mPanY; + int mFlags; + float mOutX; + float mOutY; + float mOutPanX; + float mOutPanY; + + public static final int ANCHOR_TEXT_RTL = 1; + public static final int ANCHOR_MONOSPACE_MEASURE = 2; + + public DrawTextAnchored(int textID, + float x, + float y, + float panX, + float panY, + int flags) { + mTextID = textID; + mX = x; + mY = y; + mOutX = mX; + mOutY = mY; + mFlags = flags; + mOutPanX = mPanX = panX; + mOutPanY = mPanY = panY; + } + + @Override + public void updateVariables(RemoteContext context) { + mOutX = (Float.isNaN(mX)) + ? context.getFloat(Utils.idFromNan(mX)) : mX; + mOutY = (Float.isNaN(mY)) + ? context.getFloat(Utils.idFromNan(mY)) : mY; + mOutPanX = (Float.isNaN(mPanX)) + ? context.getFloat(Utils.idFromNan(mPanX)) : mPanX; + mOutPanY = (Float.isNaN(mPanY)) + ? context.getFloat(Utils.idFromNan(mPanY)) : mPanY; + } + + @Override + public void registerListening(RemoteContext context) { + if (Float.isNaN(mX)) { + context.listensTo(Utils.idFromNan(mX), this); + } + if (Float.isNaN(mY)) { + context.listensTo(Utils.idFromNan(mY), this); + } + if (Float.isNaN(mPanX)) { + context.listensTo(Utils.idFromNan(mPanX), this); + } + if (Float.isNaN(mPanY) && Utils.idFromNan(mPanY) > 0) { + context.listensTo(Utils.idFromNan(mPanY), this); + } + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mTextID, mX, + mY, + mPanX, + mPanY, + mFlags); + } + + @Override + public String toString() { + return "DrawTextAnchored [" + mTextID + "] " + floatToStr(mX) + ", " + + floatToStr(mY) + ", " + + floatToStr(mPanX) + ", " + floatToStr(mPanY) + ", " + + Integer.toBinaryString(mFlags); + } + + private static String floatToStr(float v) { + if (Float.isNaN(v)) { + return "[" + Utils.idFromNan(v) + "]"; + } + return Float.toString(v); + } + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + int textID = buffer.readInt(); + float x = buffer.readFloat(); + float y = buffer.readFloat(); + float panX = buffer.readFloat(); + float panY = buffer.readFloat(); + int flags = buffer.readInt(); + + DrawTextAnchored op = new DrawTextAnchored(textID, + x, y, + panX, panY, + flags); + + operations.add(op); + } + + @Override + public String name() { + return ""; + } + + @Override + public int id() { + return 0; + } + + /** + * Writes out the operation to the buffer + * @param buffer + * @param textID + * @param x + * @param y + * @param panX + * @param panY + * @param flags + */ + public void apply(WireBuffer buffer, + int textID, + float x, + float y, + float panX, + float panY, + int flags) { + buffer.start(Operations.DRAW_TEXT_ANCHOR); + buffer.writeInt(textID); + buffer.writeFloat(x); + buffer.writeFloat(y); + buffer.writeFloat(panX); + buffer.writeFloat(panY); + buffer.writeInt(flags); + } + } + + float[] mBounds = new float[4]; + + private float getHorizontalOffset() { + // TODO scale TextSize / BaseTextSize; + float scale = 1.0f; + + float textWidth = scale * (mBounds[2] - mBounds[0]); + float boxWidth = 0; + return (boxWidth - textWidth) * (1 + mOutPanX) / 2.f + - (scale * mBounds[0]); + } + + private float getVerticalOffset() { + // TODO scale TextSize / BaseTextSize; + float scale = 1.0f; + float boxHeight = 0; + float textHeight = scale * (mBounds[3] - mBounds[1]); + return (boxHeight - textHeight) * (1 - mOutPanY) / 2 + - (scale * mBounds[1]); + } + + @Override + public void paint(PaintContext context) { + context.getTextBounds(mTextID, 0, -1, + (mFlags & ANCHOR_MONOSPACE_MEASURE) != 0, mBounds); + float x = mOutX + getHorizontalOffset(); + float y = (Float.isNaN(mOutPanY)) ? mOutY : mOutY + getVerticalOffset(); + context.drawTextRun(mTextID, 0, -1, 0, 1, x, y, + (mFlags & ANCHOR_TEXT_RTL) == 1); + } +} 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 1856e3097ec0..b1a01724c315 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 @@ -24,6 +24,9 @@ import com.android.internal.widget.remotecompose.core.WireBuffer; import java.util.List; +/** + * Draw text along a path. + */ public class DrawTextOnPath extends PaintOperation { public static final Companion COMPANION = new Companion(); int mPathId; @@ -45,7 +48,8 @@ public class DrawTextOnPath extends PaintOperation { @Override public String toString() { - return "DrawTextOnPath " + " " + mPathId + ";"; + return "DrawTextOnPath [" + mTextId + "] [" + mPathId + "] " + + mHOffset + ", " + mVOffset; } public static class Companion implements CompanionOperation { 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 ef0a4ad2eff3..48fc94ee5f27 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 @@ -58,7 +58,7 @@ public class DrawTweenPath extends PaintOperation { public String toString() { return "DrawTweenPath " + mPath1Id + " " + mPath2Id + " " + mTween + " " + mStart + " " - + "- " + mStop + ";"; + + "- " + mStop; } public static class Companion implements CompanionOperation { 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 new file mode 100644 index 000000000000..576b53f9fc6c --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java @@ -0,0 +1,93 @@ +/* + * 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; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +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 java.util.List; + +/** + * Operation to deal with Text data + */ +public class FloatConstant implements Operation { + public int mTextId; + public float mValue; + public static final Companion COMPANION = new Companion(); + public static final int MAX_STRING_SIZE = 4000; + + public FloatConstant(int textId, float value) { + this.mTextId = textId; + this.mValue = value; + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mTextId, mValue); + } + + @Override + public String toString() { + return "FloatConstant[" + mTextId + "] = " + mValue + ""; + } + + public static class Companion implements CompanionOperation { + private Companion() {} + + @Override + public String name() { + return "FloatExpression"; + } + + @Override + public int id() { + return Operations.DATA_FLOAT; + } + + /** + * Writes out the operation to the buffer + * @param buffer + * @param textId + * @param value + */ + public void apply(WireBuffer buffer, int textId, float value) { + buffer.start(Operations.DATA_FLOAT); + buffer.writeInt(textId); + buffer.writeFloat(value); + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + int textId = buffer.readInt(); + + float value = buffer.readFloat(); + operations.add(new FloatConstant(textId, value)); + } + } + + @Override + public void apply(RemoteContext context) { + context.loadFloat(mTextId, mValue); + } + + @Override + public String deepToString(String indent) { + return indent + toString(); + } +} 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 new file mode 100644 index 000000000000..354f41b813e0 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java @@ -0,0 +1,206 @@ +/* + * 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; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +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.operations.utilities.AnimatedFloatExpression; +import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation; + +import java.util.Arrays; +import java.util.List; + +/** + * Operation to deal with AnimatedFloats + * This is designed to be an optimized calculation for things like + * injecting the width of the component int draw rect + * As well as supporting generalized animation floats. + * The floats represent a RPN style calculator + */ +public class FloatExpression implements Operation, VariableSupport { + public int mId; + public float[] mSrcValue; + public float[] mSrcAnimation; + public FloatAnimation mFloatAnimation; + public float[] mPreCalcValue; + private float mLastChange = Float.NaN; + AnimatedFloatExpression mExp = new AnimatedFloatExpression(); + public static final Companion COMPANION = new Companion(); + public static final int MAX_STRING_SIZE = 4000; + + public FloatExpression(int id, float[] value, float[] animation) { + this.mId = id; + this.mSrcValue = value; + this.mSrcAnimation = animation; + if (mSrcAnimation != null) { + mFloatAnimation = new FloatAnimation(mSrcAnimation); + } + } + + @Override + public void updateVariables(RemoteContext context) { + if (mPreCalcValue == null || mPreCalcValue.length != mSrcValue.length) { + mPreCalcValue = new float[mSrcValue.length]; + } + //Utils.log("updateVariables "); + boolean value_changed = false; + for (int i = 0; i < mSrcValue.length; i++) { + float v = mSrcValue[i]; + if (Float.isNaN(v) && !AnimatedFloatExpression.isMathOperator(v)) { + float newValue = context.getFloat(Utils.idFromNan(v)); + if (mFloatAnimation != null) { + if (mPreCalcValue[i] != newValue) { + mLastChange = context.getAnimationTime(); + value_changed = true; + mPreCalcValue[i] = newValue; + } + } else { + mPreCalcValue[i] = newValue; + } + } else { + mPreCalcValue[i] = mSrcValue[i]; + } + } + if (value_changed && mFloatAnimation != null) { + float v = mExp.eval(Arrays.copyOf(mPreCalcValue, mPreCalcValue.length)); + if (Float.isNaN(mFloatAnimation.getTargetValue())) { + mFloatAnimation.setInitialValue(v); + } else { + mFloatAnimation.setInitialValue(mFloatAnimation.getTargetValue()); + } + mFloatAnimation.setTargetValue(v); + } + } + + @Override + public void registerListening(RemoteContext context) { + for (int i = 0; i < mSrcValue.length; i++) { + float v = mSrcValue[i]; + if (Float.isNaN(v) && !AnimatedFloatExpression.isMathOperator(v)) { + context.listensTo(Utils.idFromNan(v), this); + } + } + } + + @Override + public void apply(RemoteContext context) { + updateVariables(context); + float t = context.getAnimationTime(); + if (Float.isNaN(mLastChange)) { + mLastChange = t; + } + if (mFloatAnimation != null) { + float f = mFloatAnimation.get(t - mLastChange); + context.loadFloat(mId, f); + } else { + context.loadFloat(mId, mExp.eval(Arrays.copyOf(mPreCalcValue, mPreCalcValue.length))); + } + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mId, mSrcValue, mSrcAnimation); + } + + @Override + public String toString() { + String[] labels = new String[mSrcValue.length]; + for (int i = 0; i < mSrcValue.length; i++) { + if (Float.isNaN(mSrcValue[i])) { + labels[i] = "[" + Utils.idFromNan(mSrcValue[i]) + "]"; + } + + } + return "FloatExpression[" + mId + "] = (" + + AnimatedFloatExpression.toString(mPreCalcValue, labels) + ")"; + } + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public String name() { + return "FloatExpression"; + } + + @Override + public int id() { + return Operations.ANIMATED_FLOAT; + } + + /** + * Writes out the operation to the buffer + * @param buffer + * @param id + * @param value + * @param animation + */ + public void apply(WireBuffer buffer, int id, float[] value, float[] animation) { + buffer.start(Operations.ANIMATED_FLOAT); + buffer.writeInt(id); + + int len = value.length; + if (animation != null) { + len |= (animation.length << 16); + } + buffer.writeInt(len); + + for (int i = 0; i < value.length; i++) { + buffer.writeFloat(value[i]); + } + if (animation != null) { + for (int i = 0; i < animation.length; i++) { + buffer.writeFloat(animation[i]); + } + } + + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + int id = buffer.readInt(); + int len = buffer.readInt(); + int valueLen = len & 0xFFFF; + int animLen = (len >> 16) & 0xFFFF; + float[] values = new float[valueLen]; + for (int i = 0; i < values.length; i++) { + values[i] = buffer.readFloat(); + } + + float[] animation; + if (animLen != 0) { + animation = new float[animLen]; + for (int i = 0; i < animation.length; i++) { + animation[i] = buffer.readFloat(); + } + } else { + animation = null; + } + operations.add(new FloatExpression(id, values, animation)); + } + } + + @Override + public String deepToString(String indent) { + return indent + toString(); + } + +} 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 482e0e22bd57..0dad45ce356b 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 @@ -37,7 +37,7 @@ public class MatrixRestore extends PaintOperation { @Override public String toString() { - return "MatrixRestore;"; + return "MatrixRestore"; } public static class Companion implements CompanionOperation { 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 d6c89e0d2c64..bbf41351a1c5 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 @@ -15,68 +15,29 @@ */ package com.android.internal.widget.remotecompose.core.operations; -import com.android.internal.widget.remotecompose.core.CompanionOperation; 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.PaintOperation; -import com.android.internal.widget.remotecompose.core.WireBuffer; -import java.util.List; - -public class MatrixRotate extends PaintOperation { - public static final Companion COMPANION = new Companion(); - float mRotate, mPivotX, mPivotY; +public class MatrixRotate extends DrawBase3 { + public static final Companion COMPANION = + new Companion(Operations.MATRIX_ROTATE) { + @Override + public Operation construct(float rotate, + float pivotX, + float pivotY + ) { + return new MatrixRotate(rotate, pivotX, pivotY); + } + }; public MatrixRotate(float rotate, float pivotX, float pivotY) { - mRotate = rotate; - mPivotX = pivotX; - mPivotY = pivotY; - } - - @Override - public void write(WireBuffer buffer) { - COMPANION.apply(buffer, mRotate, mPivotX, mPivotY); - } - - @Override - public String toString() { - return "DrawArc " + mRotate + ", " + mPivotX + ", " + mPivotY + ";"; - } - - public static class Companion implements CompanionOperation { - private Companion() { - } - - @Override - public void read(WireBuffer buffer, List<Operation> operations) { - float rotate = buffer.readFloat(); - float pivotX = buffer.readFloat(); - float pivotY = buffer.readFloat(); - MatrixRotate op = new MatrixRotate(rotate, pivotX, pivotY); - operations.add(op); - } - - @Override - public String name() { - return "Matrix"; - } - - @Override - public int id() { - return Operations.MATRIX_ROTATE; - } - - public void apply(WireBuffer buffer, float rotate, float pivotX, float pivotY) { - buffer.start(Operations.MATRIX_ROTATE); - buffer.writeFloat(rotate); - buffer.writeFloat(pivotX); - buffer.writeFloat(pivotY); - } + super(rotate, pivotX, pivotY); + mName = "MatrixRotate"; } @Override public void paint(PaintContext context) { - context.matrixRotate(mRotate, mPivotX, mPivotY); + context.matrixRotate(mV1, mV2, mV3); } } 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 28aa68dd5884..04b940ba16c8 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 @@ -15,74 +15,30 @@ */ package com.android.internal.widget.remotecompose.core.operations; -import com.android.internal.widget.remotecompose.core.CompanionOperation; 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.PaintOperation; -import com.android.internal.widget.remotecompose.core.WireBuffer; -import java.util.List; - -public class MatrixScale extends PaintOperation { - public static final Companion COMPANION = new Companion(); - float mScaleX, mScaleY; - float mCenterX, mCenterY; +public class MatrixScale extends DrawBase4 { + public static final Companion COMPANION = + new Companion(Operations.MATRIX_SCALE) { + @Override + public Operation construct(float scaleX, + float scaleY, + float centerX, + float centerY + ) { + return new MatrixScale(scaleX, scaleY, centerX, centerY); + } + }; public MatrixScale(float scaleX, float scaleY, float centerX, float centerY) { - mScaleX = scaleX; - mScaleY = scaleY; - mCenterX = centerX; - mCenterY = centerY; - } - - @Override - public void write(WireBuffer buffer) { - COMPANION.apply(buffer, mScaleX, mScaleY, mCenterX, mCenterY); - } - - @Override - public String toString() { - return "MatrixScale " + mScaleY + ", " + mScaleY + ";"; - } - - public static class Companion implements CompanionOperation { - private Companion() { - } - - @Override - public void read(WireBuffer buffer, List<Operation> operations) { - float scaleX = buffer.readFloat(); - float scaleY = buffer.readFloat(); - float centerX = buffer.readFloat(); - float centerY = buffer.readFloat(); - MatrixScale op = new MatrixScale(scaleX, scaleY, centerX, centerY); - operations.add(op); - } - - @Override - public String name() { - return "Matrix"; - } - - @Override - public int id() { - return Operations.MATRIX_SCALE; - } - - public void apply(WireBuffer buffer, float scaleX, float scaleY, - float centerX, float centerY) { - buffer.start(Operations.MATRIX_SCALE); - buffer.writeFloat(scaleX); - buffer.writeFloat(scaleY); - buffer.writeFloat(centerX); - buffer.writeFloat(centerY); - - } + super(scaleX, scaleY, centerX, centerY); + mName = "MatrixScale"; } @Override public void paint(PaintContext context) { - context.mtrixScale(mScaleX, mScaleY, mCenterX, mCenterY); + context.matrixScale(mX1, mY1, mX2, mY2); } } 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 32987521e041..4f34e987e064 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 @@ -15,65 +15,28 @@ */ package com.android.internal.widget.remotecompose.core.operations; -import com.android.internal.widget.remotecompose.core.CompanionOperation; 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.PaintOperation; -import com.android.internal.widget.remotecompose.core.WireBuffer; -import java.util.List; - -public class MatrixTranslate extends PaintOperation { - public static final Companion COMPANION = new Companion(); - float mTranslateX, mTranslateY; +public class MatrixTranslate extends DrawBase2 { + public static final Companion COMPANION = + new Companion(Operations.MATRIX_TRANSLATE) { + @Override + public Operation construct(float x1, + float y1 + ) { + return new MatrixTranslate(x1, y1); + } + }; public MatrixTranslate(float translateX, float translateY) { - mTranslateX = translateX; - mTranslateY = translateY; - } - - @Override - public void write(WireBuffer buffer) { - COMPANION.apply(buffer, mTranslateX, mTranslateY); - } - - @Override - public String toString() { - return "DrawArc " + mTranslateY + ", " + mTranslateY + ";"; - } - - public static class Companion implements CompanionOperation { - private Companion() { - } - - @Override - public void read(WireBuffer buffer, List<Operation> operations) { - float translateX = buffer.readFloat(); - float translateY = buffer.readFloat(); - MatrixTranslate op = new MatrixTranslate(translateX, translateY); - operations.add(op); - } - - @Override - public String name() { - return "Matrix"; - } - - @Override - public int id() { - return Operations.MATRIX_TRANSLATE; - } - - public void apply(WireBuffer buffer, float translateX, float translateY) { - buffer.start(Operations.MATRIX_TRANSLATE); - buffer.writeFloat(translateX); - buffer.writeFloat(translateY); - } + super(translateX, translateY); + mName = "MatrixTranslate"; } @Override public void paint(PaintContext context) { - context.matrixTranslate(mTranslateX, mTranslateY); + context.matrixTranslate(mV1, mV2); } } 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 new file mode 100644 index 000000000000..0c5b286684d4 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java @@ -0,0 +1,99 @@ +/* + * 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; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +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 java.util.List; + +/** + * Operation to deal with Text data + */ +public class NamedVariable implements Operation { + public int mVarId; + public String mVarName; + public int mVarType; + public static final Companion COMPANION = new Companion(); + public static final int MAX_STRING_SIZE = 4000; + + public NamedVariable(int varId, int varType, String name) { + this.mVarId = varId; + this.mVarType = varType; + this.mVarName = name; + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mVarId, mVarType, mVarName); + } + + @Override + public String toString() { + return "VariableName[" + mVarId + "] = \"" + + Utils.trimString(mVarName, 10) + "\" type=" + mVarType; + } + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public String name() { + return "TextData"; + } + + @Override + public int id() { + return Operations.DATA_TEXT; + } + + /** + * Writes out the operation to the buffer + * @param buffer + * @param varId + * @param varType + * @param text + */ + public void apply(WireBuffer buffer, int varId, int varType, String text) { + buffer.start(Operations.DATA_TEXT); + buffer.writeInt(varId); + buffer.writeInt(varType); + buffer.writeUTF8(text); + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + int varId = buffer.readInt(); + int varType = buffer.readInt(); + String text = buffer.readUTF8(MAX_STRING_SIZE); + operations.add(new NamedVariable(varId, varType, text)); + } + } + + @Override + public void apply(RemoteContext context) { + context.loadVariableName(mVarName, mVarId, mVarType); + } + + @Override + public String deepToString(String indent) { + return indent + toString(); + } +} 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 e5683ece7919..0807bcdcfebb 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 @@ -20,12 +20,14 @@ 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.PaintOperation; +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.operations.paint.PaintBundle; import java.util.List; -public class PaintData extends PaintOperation { +public class PaintData extends PaintOperation implements VariableSupport { public PaintBundle mPaintData = new PaintBundle(); public static final Companion COMPANION = new Companion(); public static final int MAX_STRING_SIZE = 4000; @@ -34,6 +36,16 @@ public class PaintData extends PaintOperation { } @Override + public void updateVariables(RemoteContext context) { + mPaintData.updateVariables(context); + } + + @Override + public void registerListening(RemoteContext context) { + mPaintData.registerVars(context, this); + } + + @Override public void write(WireBuffer buffer) { COMPANION.apply(buffer, mPaintData); } 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 2646b27b1f51..e467e7b7f31e 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 @@ -18,27 +18,50 @@ package com.android.internal.widget.remotecompose.core.operations; import com.android.internal.widget.remotecompose.core.CompanionOperation; 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.VariableSupport; import com.android.internal.widget.remotecompose.core.WireBuffer; +import java.util.Arrays; import java.util.List; -public class PathData implements Operation { +public class PathData implements Operation, VariableSupport { public static final Companion COMPANION = new Companion(); int mInstanceId; - float[] mRef; float[] mFloatPath; - float[] mRetFloats; + float[] mOutputPath; PathData(int instanceId, float[] floatPath) { mInstanceId = instanceId; mFloatPath = floatPath; + mOutputPath = Arrays.copyOf(mFloatPath, mFloatPath.length); + } + + @Override + public void updateVariables(RemoteContext context) { + for (int i = 0; i < mFloatPath.length; i++) { + float v = mFloatPath[i]; + if (Utils.isVariable(v)) { + mOutputPath[i] = (Float.isNaN(v)) + ? context.getFloat(Utils.idFromNan(v)) : v; + } else { + mOutputPath[i] = v; + } + } + } + + @Override + public void registerListening(RemoteContext context) { + for (int i = 0; i < mFloatPath.length; i++) { + if (Float.isNaN(mFloatPath[i])) { + context.listensTo(Utils.idFromNan(mFloatPath[i]), this); + } + } } @Override public void write(WireBuffer buffer) { - COMPANION.apply(buffer, mInstanceId, mFloatPath); + COMPANION.apply(buffer, mInstanceId, mOutputPath); } @Override @@ -46,29 +69,35 @@ public class PathData implements Operation { return pathString(mFloatPath); } - public float[] getFloatPath(PaintContext context) { - float[] ret = mRetFloats; // Assume retFloats is declared elsewhere - if (ret == null) { - return mFloatPath; // Assume floatPath is declared elsewhere - } - float[] localRef = mRef; // Assume ref is of type Float[] - if (localRef == null) { - for (int i = 0; i < mFloatPath.length; i++) { - ret[i] = mFloatPath[i]; - } - } else { - for (int i = 0; i < mFloatPath.length; i++) { - float lr = localRef[i]; - if (Float.isNaN(lr)) { - ret[i] = Utils.getActualValue(lr); - } else { - ret[i] = mFloatPath[i]; - } - } - } - return ret; + @Override + public String toString() { + return "PathData[" + mInstanceId + "] = " + "\"" + deepToString(" ") + "\""; } + /** + * public float[] getFloatPath(PaintContext context) { + * float[] ret = mRetFloats; // Assume retFloats is declared elsewhere + * if (ret == null) { + * return mFloatPath; // Assume floatPath is declared elsewhere + * } + * float[] localRef = mRef; // Assume ref is of type Float[] + * if (localRef == null) { + * for (int i = 0; i < mFloatPath.length; i++) { + * ret[i] = mFloatPath[i]; + * } + * } else { + * for (int i = 0; i < mFloatPath.length; i++) { + * float lr = localRef[i]; + * if (Float.isNaN(lr)) { + * ret[i] = Utils.getActualValue(lr); + * } else { + * ret[i] = mFloatPath[i]; + * } + * } + * } + * return ret; + * } + */ public static final int MOVE = 10; public static final int LINE = 11; public static final int QUADRATIC = 12; @@ -155,7 +184,7 @@ public class PathData implements Operation { str.append("."); break; default: - str.append("X"); + str.append("[" + id + "]"); break; } } else { @@ -170,7 +199,7 @@ public class PathData implements Operation { @Override public void apply(RemoteContext context) { - context.loadPathData(mInstanceId, mFloatPath); + context.loadPathData(mInstanceId, mOutputPath); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java index 6d924eb70c50..997e8dc791ed 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java @@ -94,7 +94,6 @@ public class RootContentBehavior implements RemoteComposeOperation { public static final int SCALE_CROP = 5; public static final int SCALE_FILL_BOUNDS = 6; - public static final Companion COMPANION = new Companion(); /** 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 64c7f3ef2d44..076b28edf981 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 @@ -48,7 +48,7 @@ public class RootContentDescription implements RemoteComposeOperation { @Override public String toString() { - return "ROOT_CONTENT_DESCRIPTION " + mContentDescription; + return "RootContentDescription " + mContentDescription; } @Override 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 new file mode 100644 index 000000000000..8463ac576774 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java @@ -0,0 +1,309 @@ +/* + * 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; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +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 java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +/** + * Operation to deal with bitmap data + * On getting an Image during a draw call the bitmap is compressed and saved + * in playback the image is decompressed + */ +public class ShaderData implements Operation, VariableSupport { + int mShaderTextId; // the actual text of a shader + int mShaderID; // allows shaders to be referenced by number + HashMap<String, float[]> mUniformRawFloatMap = null; + HashMap<String, float[]> mUniformFloatMap = null; + HashMap<String, int[]> mUniformIntMap = null; + HashMap<String, Integer> mUniformBitmapMap = null; + + public static final int MAX_IMAGE_DIMENSION = 8000; + + public static final Companion COMPANION = new Companion(); + + public ShaderData(int shaderID, + int shaderTextId, + HashMap<String, float[]> floatMap, + HashMap<String, int[]> intMap, + HashMap<String, Integer> bitmapMap) { + mShaderID = shaderID; + mShaderTextId = shaderTextId; + if (floatMap != null) { + mUniformFloatMap = new HashMap<>(); + mUniformRawFloatMap = new HashMap<>(); + + for (String name : floatMap.keySet()) { + mUniformRawFloatMap.put(name, floatMap.get(name)); + mUniformFloatMap.put(name, floatMap.get(name)); + } + } + + if (intMap != null) { + mUniformIntMap = new HashMap<>(); + for (String name : intMap.keySet()) { + mUniformIntMap.put(name, intMap.get(name)); + } + } + if (bitmapMap != null) { + mUniformBitmapMap = new HashMap<>(); + for (String name : bitmapMap.keySet()) { + mUniformBitmapMap.put(name, bitmapMap.get(name)); + } + } + + } + + public int getShaderTextId() { + return mShaderTextId; + } + + /** + * get names of all known floats + * @return + */ + public String[] getUniformFloatNames() { + if (mUniformFloatMap == null) return new String[0]; + return mUniformFloatMap.keySet().toArray(new String[0]); + } + + /** + * Get float values associated with the name + * @param name + * @return + */ + public float[] getUniformFloats(String name) { + return mUniformFloatMap.get(name); + } + + /** + * get the name of all know uniform integers + * @return + */ + public String[] getUniformIntegerNames() { + if (mUniformIntMap == null) return new String[0]; + return mUniformIntMap.keySet().toArray(new String[0]); + } + + /** + * Get Int value associated with the name + * @param name + * @return + */ + public int[] getUniformInts(String name) { + return mUniformIntMap.get(name); + } + + /** + * get list of uniform Bitmaps + * @return + */ + public String[] getUniformBitmapNames() { + if (mUniformBitmapMap == null) return new String[0]; + return mUniformBitmapMap.keySet().toArray(new String[0]); + } + + /** + * Get a bitmap stored under that name + * @param name + * @return + */ + public int getUniformBitmapId(String name) { + return mUniformBitmapMap.get(name); + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mShaderID, mShaderTextId, + mUniformFloatMap, mUniformIntMap, mUniformBitmapMap); + } + + @Override + public String toString() { + return "SHADER DATA " + mShaderID; + } + + @Override + public void updateVariables(RemoteContext context) { + for (String name : mUniformRawFloatMap.keySet()) { + float[] value = mUniformRawFloatMap.get(name); + float[] out = null; + for (int i = 0; i < value.length; i++) { + if (Float.isNaN(value[i])) { + if (out == null) { // need to copy + out = Arrays.copyOf(value, value.length); + } + out[i] = context.getFloat(Utils.idFromNan(value[i])); + } + } + mUniformFloatMap.put(name, out == null ? value : out); + } + } + + @Override + public void registerListening(RemoteContext context) { + for (String name : mUniformRawFloatMap.keySet()) { + float[] value = mUniformRawFloatMap.get(name); + for (int i = 0; i < value.length; i++) { + if (Float.isNaN(value[i])) { + context.listensTo(Utils.idFromNan(value[i]), this); + } + } + } + } + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public String name() { + return "BitmapData"; + } + + @Override + public int id() { + return Operations.DATA_SHADER; + } + + /** + * Writes out the operation to the buffer + * @param buffer + * @param shaderID + * @param shaderTextId + * @param floatMap + * @param intMap + * @param bitmapMap + */ + public void apply(WireBuffer buffer, int shaderID, int shaderTextId, + HashMap<String, float[]> floatMap, + HashMap<String, int[]> intMap, + HashMap<String, Integer> bitmapMap) { + buffer.start(Operations.DATA_SHADER); + buffer.writeInt(shaderID); + + buffer.writeInt(shaderTextId); + int floatSize = (floatMap == null) ? 0 : floatMap.size(); + int intSize = (intMap == null) ? 0 : intMap.size(); + int bitmapSize = (bitmapMap == null) ? 0 : bitmapMap.size(); + int sizes = floatSize | (intSize << 8) | (bitmapSize << 16); + buffer.writeInt(sizes); + + if (floatSize > 0) { + + for (String name : floatMap.keySet()) { + buffer.writeUTF8(name); + float[] values = floatMap.get(name); + buffer.writeInt(values.length); + + for (int i = 0; i < values.length; i++) { + buffer.writeFloat(values[i]); + } + } + } + + if (intSize > 0) { + for (String name : intMap.keySet()) { + buffer.writeUTF8(name); + int[] values = intMap.get(name); + buffer.writeInt(values.length); + for (int i = 0; i < values.length; i++) { + buffer.writeInt(values[i]); + } + } + } + if (bitmapSize > 0) { + for (String name : bitmapMap.keySet()) { + buffer.writeUTF8(name); + int value = bitmapMap.get(name); + buffer.writeInt(value); + } + } + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + int shaderID = buffer.readInt(); + int shaderTextId = buffer.readInt(); + HashMap<String, float[]> floatMap = null; + HashMap<String, int[]> intMap = null; + HashMap<String, Integer> bitmapMap = null; + + int sizes = buffer.readInt(); + + int floatMapSize = sizes & 0xFF; + if (floatMapSize > 0) { + floatMap = new HashMap<>(); + for (int i = 0; i < floatMapSize; i++) { + String name = buffer.readUTF8(); + int len = buffer.readInt(); + float[] val = new float[len]; + + for (int j = 0; j < len; j++) { + val[j] = buffer.readFloat(); + } + + floatMap.put(name, val); + } + } + int intMapSize = (sizes >> 8) & 0xFF; + + if (intMapSize > 0) { + + intMap = new HashMap<>(); + for (int i = 0; i < intMapSize; i++) { + String name = buffer.readUTF8(); + int len = buffer.readInt(); + int[] val = new int[len]; + for (int j = 0; j < len; j++) { + val[j] = buffer.readInt(); + } + intMap.put(name, val); + } + } + int bitmapMapSize = (sizes >> 16) & 0xFF; + + if (bitmapMapSize > 0) { + bitmapMap = new HashMap<>(); + for (int i = 0; i < bitmapMapSize; i++) { + String name = buffer.readUTF8(); + int val = buffer.readInt(); + bitmapMap.put(name, val); + } + } + operations.add(new ShaderData(shaderID, shaderTextId, + floatMap, intMap, bitmapMap)); + } + } + + @Override + public void apply(RemoteContext context) { + context.loadShader(mShaderID, this); + } + + @Override + public String deepToString(String indent) { + return indent + toString(); + } +} 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 5b622ae96d0b..ed1344975256 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 @@ -44,11 +44,13 @@ public class TextData implements Operation { @Override public String toString() { - return "TEXT DATA " + mTextId + "\"" + mText + "\""; + return "TextData[" + mTextId + "] = \"" + + Utils.trimString(mText, 10) + "\""; } public static class Companion implements CompanionOperation { - private Companion() {} + private Companion() { + } @Override public String name() { 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 new file mode 100644 index 000000000000..65a39a1eba04 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java @@ -0,0 +1,172 @@ +/* + * 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; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +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.operations.utilities.StringUtils; + +import java.util.List; + +/** + * Operation convert floats to text + * This command is structured [command][textID][before,after][flags] + * before and after define number of digits before and after the decimal point + */ +public class TextFromFloat implements Operation, VariableSupport { + public int mTextId; + public float mValue; + public float mOutValue; + public short mDigitsBefore; + public short mDigitsAfter; + public int mFlags; + public static final Companion COMPANION = new Companion(); + public static final int MAX_STRING_SIZE = 4000; + char mPre = ' '; + char mAfter = ' '; + // Theses flags define what how to/if fill the space + public static final int PAD_AFTER_SPACE = 0; // pad past point with space + public static final int PAD_AFTER_NONE = 1; // do not pad past last digit + public static final int PAD_AFTER_ZERO = 3; // pad with 0 past last digit + public static final int PAD_PRE_SPACE = 0; // pad before number with spaces + public static final int PAD_PRE_NONE = 4; // pad before number with 0s + public static final int PAD_PRE_ZERO = 12; // do not pad before number + + public TextFromFloat(int textId, float value, short digitsBefore, + short digitsAfter, int flags) { + this.mTextId = textId; + this.mValue = value; + this.mDigitsAfter = digitsAfter; + this.mDigitsBefore = digitsBefore; + this.mFlags = flags; + mOutValue = mValue; + switch (mFlags & 3) { + case PAD_AFTER_SPACE: + mAfter = ' '; + break; + case PAD_AFTER_NONE: + mAfter = 0; + break; + case PAD_AFTER_ZERO: + mAfter = '0'; + break; + } + switch (mFlags & 12) { + case PAD_PRE_SPACE: + mPre = ' '; + break; + case PAD_PRE_NONE: + mPre = 0; + break; + case PAD_PRE_ZERO: + mPre = '0'; + break; + } + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mTextId, mValue, mDigitsAfter, mDigitsBefore, mFlags); + } + + @Override + public String toString() { + return "TextFromFloat[" + mTextId + "] = " + + Utils.floatToString(mValue) + " " + mDigitsBefore + + "." + mDigitsAfter + " " + mFlags; + } + + + @Override + public void updateVariables(RemoteContext context) { + if (Float.isNaN(mValue)) { + mOutValue = context.getFloat(Utils.idFromNan(mValue)); + } + + } + + + @Override + public void registerListening(RemoteContext context) { + if (Float.isNaN(mValue)) { + context.listensTo(Utils.idFromNan(mValue), this); + } + } + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public String name() { + return "TextData"; + } + + @Override + public int id() { + return Operations.TEXT_FROM_FLOAT; + } + + /** + * Writes out the operation to the buffer + * @param buffer + * @param textId + * @param value + * @param digitsBefore + * @param digitsAfter + * @param flags + */ + public void apply(WireBuffer buffer, int textId, + float value, short digitsBefore, + short digitsAfter, int flags) { + buffer.start(Operations.TEXT_FROM_FLOAT); + buffer.writeInt(textId); + buffer.writeFloat(value); + buffer.writeInt((digitsBefore << 16) | digitsAfter); + buffer.writeInt(flags); + + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + int textId = buffer.readInt(); + float value = buffer.readFloat(); + int tmp = buffer.readInt(); + short post = (short) (tmp & 0xFFFF); + short pre = (short) ((tmp >> 16) & 0xFFFF); + + int flags = buffer.readInt(); + operations.add(new TextFromFloat(textId, value, pre, post, flags)); + } + } + + @Override + public void apply(RemoteContext context) { + float v = mOutValue; + String s = StringUtils.floatToString(v, mDigitsBefore, + mDigitsAfter, mPre, mAfter); + context.loadText(mTextId, s); + } + + @Override + public String deepToString(String indent) { + return indent + toString(); + } +} 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 new file mode 100644 index 000000000000..a0fc854f38f4 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java @@ -0,0 +1,101 @@ +/* + * 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; + +import com.android.internal.widget.remotecompose.core.CompanionOperation; +import com.android.internal.widget.remotecompose.core.Operation; +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 java.util.List; + +/** + * Operation to deal with Text data + */ +public class TextMerge implements Operation { + public int mTextId; + public int mSrcId1; + public int mSrcId2; + public static final Companion COMPANION = new Companion(); + public static final int MAX_STRING_SIZE = 4000; + + public TextMerge(int textId, int srcId1, int srcId2) { + this.mTextId = textId; + this.mSrcId1 = srcId1; + this.mSrcId2 = srcId2; + } + + @Override + public void write(WireBuffer buffer) { + COMPANION.apply(buffer, mTextId, mSrcId1, mSrcId2); + } + + @Override + public String toString() { + return "TextMerge[" + mTextId + "] = [" + mSrcId1 + " ] + [ " + mSrcId2 + "]"; + } + + public static class Companion implements CompanionOperation { + private Companion() { + } + + @Override + public String name() { + return "TextData"; + } + + @Override + public int id() { + return Operations.TEXT_MERGE; + } + + /** + * Writes out the operation to the buffer + * @param buffer + * @param textId + * @param srcId1 + * @param srcId2 + */ + public void apply(WireBuffer buffer, int textId, int srcId1, int srcId2) { + buffer.start(Operations.TEXT_MERGE); + buffer.writeInt(textId); + buffer.writeInt(srcId1); + buffer.writeInt(srcId2); + } + + @Override + public void read(WireBuffer buffer, List<Operation> operations) { + int textId = buffer.readInt(); + int srcId1 = buffer.readInt(); + int srcId2 = buffer.readInt(); + + operations.add(new TextMerge(textId, srcId1, srcId2)); + } + } + + @Override + public void apply(RemoteContext context) { + String str1 = context.getText(mSrcId1); + String str2 = context.getText(mSrcId2); + context.loadText(mTextId, str1 + str2); + } + + @Override + public String deepToString(String indent) { + return indent + toString(); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java index 00e2f2058e89..fdc68601bf4d 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java @@ -15,13 +15,16 @@ */ package com.android.internal.widget.remotecompose.core.operations; +/** + * Utilities to be used across all core operations + */ public class Utils { public static float asNan(int v) { return Float.intBitsToFloat(v | -0x800000); } public static int idFromNan(float value) { - int b = Float.floatToRawIntBits(value); + int b = Float.floatToRawIntBits(value); return b & 0xFFFFF; } @@ -29,13 +32,194 @@ public class Utils { return 0; } - String getFloatString(float value) { + /** + * trim a string to n characters if needing to trim + * end in "..." + * + * @param str + * @param n + * @return + */ + static String trimString(String str, int n) { + if (str.length() > n) { + str = str.substring(0, n - 3) + "..."; + } + return str; + } + + /** + * print the id and the value of a float + * @param idvalue + * @param value + * @return + */ + public static String floatToString(float idvalue, float value) { + if (Float.isNaN(idvalue)) { + return "[" + idFromNan(idvalue) + "]" + floatToString(value); + } + return floatToString(value); + } + + /** + * Convert float to string but render nan id in brackets [n] + * @param value + * @return + */ + public static String floatToString(float value) { if (Float.isNaN(value)) { - int id = idFromNan(value); - if (id > 0) { - return "NaN(" + id + ")"; - } + return "[" + idFromNan(value) + "]"; } - return "" + value; + return Float.toString(value); + } + + /** + * Debugging util to print a message and include the file/line it came from + * @param str + */ + public static void log(String str) { + StackTraceElement s = new Throwable().getStackTrace()[1]; + System.out.println("(" + s.getFileName() + ":" + s.getLineNumber() + ")." + str); + } + + /** + * Debugging util to print the stack + * @param str + * @param n + */ + public static void logStack(String str, int n) { + StackTraceElement[] st = new Throwable().getStackTrace(); + for (int i = 1; i < n + 1; i++) { + StackTraceElement s = st[i]; + String space = new String(new char[i]).replace('\0', ' '); + System.out.println(space + "(" + s.getFileName() + + ":" + s.getLineNumber() + ")." + str); + } + } + + /** + * Is a variable Allowed int calculation and references. + * + * @param v + * @return + */ + public static boolean isVariable(float v) { + if (Float.isNaN(v)) { + int id = idFromNan(v); + return id > 40 || id < 10; + } + return false; + } + + /** + * print a color in the familiar 0xAARRGGBB pattern + * + * @param color + * @return + */ + public static String colorInt(int color) { + String str = "000000000000" + Integer.toHexString(color); + return "0x" + str.substring(str.length() - 8); } + + /** + * Interpolate two colors. + * gamma corrected colors are interpolated in the form c1 * (1-t) + c2 * t + * + * @param c1 + * @param c2 + * @param t + * @return + */ + public static int interpolateColor(int c1, int c2, float t) { + if (Float.isNaN(t) || t == 0.0f) { + return c1; + } else if (t == 1.0f) { + return c2; + } + int a = 0xFF & (c1 >> 24); + int r = 0xFF & (c1 >> 16); + int g = 0xFF & (c1 >> 8); + int b = 0xFF & c1; + float f_r = (float) Math.pow(r / 255.0f, 2.2); + float f_g = (float) Math.pow(g / 255.0f, 2.2); + float f_b = (float) Math.pow(b / 255.0f, 2.2); + float c1fr = f_r; + float c1fg = f_g; + float c1fb = f_b; + float c1fa = a / 255f; + + a = 0xFF & (c2 >> 24); + r = 0xFF & (c2 >> 16); + g = 0xFF & (c2 >> 8); + b = 0xFF & c2; + f_r = (float) Math.pow(r / 255.0f, 2.2); + f_g = (float) Math.pow(g / 255.0f, 2.2); + f_b = (float) Math.pow(b / 255.0f, 2.2); + float c2fr = f_r; + float c2fg = f_g; + float c2fb = f_b; + float c2fa = a / 255f; + f_r = c1fr + t * (c2fr - c1fr); + f_g = c1fg + t * (c2fg - c1fg); + f_b = c1fb + t * (c2fb - c1fb); + float f_a = c1fa + t * (c2fa - c1fa); + + int outr = clamp((int) ((float) Math.pow(f_r, 1.0 / 2.2) * 255.0f)); + int outg = clamp((int) ((float) Math.pow(f_g, 1.0 / 2.2) * 255.0f)); + int outb = clamp((int) ((float) Math.pow(f_b, 1.0 / 2.2) * 255.0f)); + int outa = clamp((int) (f_a * 255.0f)); + + + return (outa << 24 | outr << 16 | outg << 8 | outb); + } + + /** + * Efficient clamping function + * + * @param c + * @return number between 0 and 255 + */ + public static int clamp(int c) { + int n = 255; + c &= ~(c >> 31); + c -= n; + c &= (c >> 31); + c += n; + return c; + } + + /** + * convert hue saturation and value to RGB + * + * @param hue 0..1 + * @param saturation 0..1 0=on the gray scale + * @param value 0..1 0=black + * @return + */ + public static int hsvToRgb(float hue, float saturation, float value) { + int h = (int) (hue * 6); + float f = hue * 6 - h; + int p = (int) (0.5f + 255 * value * (1 - saturation)); + int q = (int) (0.5f + 255 * value * (1 - f * saturation)); + int t = (int) (0.5f + 255 * value * (1 - (1 - f) * saturation)); + int v = (int) (0.5f + 255 * value); + switch (h) { + case 0: + return 0XFF000000 | (v << 16) + (t << 8) + p; + case 1: + return 0XFF000000 | (q << 16) + (v << 8) + p; + case 2: + return 0XFF000000 | (p << 16) + (v << 8) + t; + case 3: + return 0XFF000000 | (p << 16) + (q << 8) + v; + case 4: + return 0XFF000000 | (t << 16) + (p << 8) + v; + case 5: + return 0XFF000000 | (v << 16) + (p << 8) + q; + + } + return 0; + } + + } 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 8abb0bfff338..a7d0ac6330f7 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 @@ -15,43 +15,60 @@ */ package com.android.internal.widget.remotecompose.core.operations.paint; +import com.android.internal.widget.remotecompose.core.PaintContext; +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.operations.Utils; import java.util.Arrays; +/** + * Paint Bundle represents a delta of changes to a paint object + */ public class PaintBundle { int[] mArray = new int[200]; + int[] mOutArray = null; int mPos = 0; - public void applyPaintChange(PaintChanges p) { + /** + * Apply changes to a PaintChanges interface + * @param paintContext + * @param p + */ + public void applyPaintChange(PaintContext paintContext, PaintChanges p) { int i = 0; int mask = 0; + if (mOutArray == null) { + mOutArray = mArray; + } while (i < mPos) { - int cmd = mArray[i++]; + int cmd = mOutArray[i++]; mask = mask | (1 << (cmd - 1)); switch (cmd & 0xFFFF) { case TEXT_SIZE: { - p.setTextSize(Float.intBitsToFloat(mArray[i++])); + p.setTextSize(Float.intBitsToFloat(mOutArray[i++])); break; } case TYPEFACE: int style = (cmd >> 16); int weight = style & 0x3ff; boolean italic = (style >> 10) > 0; - int font_type = mArray[i++]; + int font_type = mOutArray[i++]; p.setTypeFace(font_type, weight, italic); break; + case COLOR_ID: // mOutArray should have already decoded it case COLOR: { - p.setColor(mArray[i++]); + p.setColor(mOutArray[i++]); break; } case STROKE_WIDTH: { - p.setStrokeWidth(Float.intBitsToFloat(mArray[i++])); + p.setStrokeWidth(Float.intBitsToFloat(mOutArray[i++])); break; } case STROKE_MITER: { - p.setStrokeMiter(Float.intBitsToFloat(mArray[i++])); + p.setStrokeMiter(Float.intBitsToFloat(mOutArray[i++])); break; } case STROKE_CAP: { @@ -63,6 +80,7 @@ public class PaintBundle { break; } case SHADER: { + p.setShader(mOutArray[i++]); break; } case STROKE_JOIN: { @@ -81,17 +99,16 @@ public class PaintBundle { p.setFilterBitmap(!((cmd >> 16) == 0)); break; } - case GRADIENT: { - i = callSetGradient(cmd, mArray, i, p); + i = callSetGradient(cmd, mOutArray, i, p); break; } case COLOR_FILTER: { - p.setColorFilter(mArray[i++], cmd >> 16); + p.setColorFilter(mOutArray[i++], cmd >> 16); break; } case ALPHA: { - p.setAlpha(Float.intBitsToFloat(mArray[i++])); + p.setAlpha(Float.intBitsToFloat(mOutArray[i++])); break; } } @@ -106,7 +123,6 @@ public class PaintBundle { switch (id) { case TEXT_SIZE: return "TEXT_SIZE"; - case COLOR: return "COLOR"; case STROKE_WIDTH: @@ -133,7 +149,6 @@ public class PaintBundle { return "ALPHA"; case COLOR_FILTER: return "COLOR_FILTER"; - } return "????" + id + "????"; } @@ -154,6 +169,14 @@ public class PaintBundle { return str + "]"; } + private static String asFloatStr(int value) { + float fValue = Float.intBitsToFloat(value); + if (Float.isNaN(fValue)) { + return "[" + Utils.idFromNan(fValue) + "]"; + } + return Float.toString(fValue); + } + @Override public String toString() { StringBuilder ret = new StringBuilder("\n"); @@ -164,7 +187,8 @@ public class PaintBundle { switch (type) { case TEXT_SIZE: { - ret.append(" TextSize(" + Float.intBitsToFloat(mArray[i++])); + ret.append(" TextSize(" + + asFloatStr(mArray[i++])); } break; @@ -181,14 +205,18 @@ public class PaintBundle { ret.append(" Color(" + colorInt(mArray[i++])); } break; + case COLOR_ID: { + ret.append(" ColorId([" + mArray[i++] + "]"); + } + break; case STROKE_WIDTH: { ret.append(" StrokeWidth(" - + (Float.intBitsToFloat(mArray[i++]))); + + (asFloatStr(mArray[i++]))); } break; case STROKE_MITER: { ret.append(" StrokeMiter(" - + (Float.intBitsToFloat(mArray[i++]))); + + (asFloatStr(mArray[i++]))); } break; case STROKE_CAP: { @@ -207,11 +235,12 @@ public class PaintBundle { } break; case SHADER: { + ret.append(" Shader(" + mArray[i++]); } break; case ALPHA: { ret.append(" Alpha(" - + (Float.intBitsToFloat(mArray[i++]))); + + (asFloatStr(mArray[i++]))); } break; case IMAGE_FILTER_QUALITY: { @@ -244,7 +273,6 @@ public class PaintBundle { return ret.toString(); } - int callPrintGradient(int cmd, int[] array, int i, StringBuilder p) { int ret = i; int type = (cmd >> 16); @@ -258,26 +286,25 @@ public class PaintBundle { colors = new int[len]; for (int j = 0; j < colors.length; j++) { colors[j] = array[ret++]; - } } len = array[ret++]; - float[] stops = null; + String[] stops = null; if (len > 0) { - stops = new float[len]; + stops = new String[len]; for (int j = 0; j < stops.length; j++) { - stops[j] = Float.intBitsToFloat(array[ret++]); + stops[j] = asFloatStr(array[ret++]); } } p.append(" colors = " + colorInt(colors) + ",\n"); p.append(" stops = " + Arrays.toString(stops) + ",\n"); p.append(" start = "); - p.append("[" + Float.intBitsToFloat(array[ret++])); - p.append(", " + Float.intBitsToFloat(array[ret++]) + "],\n"); + p.append("[" + asFloatStr(array[ret++])); + p.append(", " + asFloatStr(array[ret++]) + "],\n"); p.append(" end = "); - p.append("[" + Float.intBitsToFloat(array[ret++])); - p.append(", " + Float.intBitsToFloat(array[ret++]) + "],\n"); + p.append("[" + asFloatStr(array[ret++])); + p.append(", " + asFloatStr(array[ret++]) + "],\n"); int tileMode = array[ret++]; p.append(" tileMode = " + tileMode + "\n "); } @@ -295,21 +322,21 @@ public class PaintBundle { } } len = array[ret++]; - float[] stops = null; + String[] stops = null; if (len > 0) { - stops = new float[len]; + stops = new String[len]; for (int j = 0; j < stops.length; j++) { - stops[j] = Float.intBitsToFloat(array[ret++]); + stops[j] = asFloatStr(array[ret++]); } } p.append(" colors = " + colorInt(colors) + ",\n"); p.append(" stops = " + Arrays.toString(stops) + ",\n"); p.append(" center = "); - p.append("[" + Float.intBitsToFloat(array[ret++])); - p.append(", " + Float.intBitsToFloat(array[ret++]) + "],\n"); + p.append("[" + asFloatStr(array[ret++])); + p.append(", " + asFloatStr(array[ret++]) + "],\n"); p.append(" radius ="); - p.append(" " + Float.intBitsToFloat(array[ret++]) + ",\n"); + p.append(" " + asFloatStr(array[ret++]) + ",\n"); int tileMode = array[ret++]; p.append(" tileMode = " + tileMode + "\n "); } @@ -327,20 +354,19 @@ public class PaintBundle { } } len = array[ret++]; - float[] stops = null; + String[] stops = null; if (len > 0) { - stops = new float[len]; + stops = new String[len]; for (int j = 0; j < stops.length; j++) { - stops[j] = Float.intBitsToFloat(array[ret++]); + stops[j] = asFloatStr(array[ret++]); } } - p.append(" colors = " + colorInt(colors) + ",\n"); p.append(" stops = " + Arrays.toString(stops) + ",\n"); p.append(" center = "); - p.append("[" + Float.intBitsToFloat(array[ret++])); - p.append(", " + Float.intBitsToFloat(array[ret++]) + "],\n "); - + p.append("[" + asFloatStr(array[ret++])); + p.append(", " + + asFloatStr(array[ret++]) + "],\n "); } break; default: { @@ -376,7 +402,6 @@ public class PaintBundle { return ret; } - switch (gradientType) { case LINEAR_GRADIENT: { @@ -433,7 +458,7 @@ public class PaintBundle { public static final int COLOR = 4; // int public static final int STROKE_WIDTH = 5; // float public static final int STROKE_MITER = 6; - public static final int STROKE_CAP = 7; // int + public static final int STROKE_CAP = 7; // int public static final int STYLE = 8; // int public static final int SHADER = 9; // int public static final int IMAGE_FILTER_QUALITY = 10; // int @@ -445,7 +470,7 @@ public class PaintBundle { public static final int TYPEFACE = 16; public static final int FILTER_BITMAP = 17; public static final int BLEND_MODE = 18; - + public static final int COLOR_ID = 19; // int public static final int BLEND_MODE_CLEAR = 0; public static final int BLEND_MODE_SRC = 1; @@ -634,8 +659,8 @@ public class PaintBundle { /** * @param fontType 0 = default 1 = sans serif 2 = serif 3 = monospace - * @param weight 100-1000 - * @param italic tur + * @param weight 100-1000 + * @param italic tur */ public void setTextStyle(int fontType, int weight, boolean italic) { int style = (weight & 0x3FF) | (italic ? 2048 : 0); // pack the weight and italic @@ -658,6 +683,10 @@ public class PaintBundle { mPos++; } + /** + * Set the Color based on Color + * @param color + */ public void setColor(int color) { mArray[mPos] = COLOR; mPos++; @@ -666,6 +695,18 @@ public class PaintBundle { } /** + * Set the Color based on ID + * @param color + */ + public void setColorId(int color) { + mArray[mPos] = COLOR_ID; + mPos++; + mArray[mPos] = color; + mPos++; + } + + + /** * Set the paint's Cap. * * @param cap set the paint's line cap style, used whenever the paint's @@ -676,16 +717,29 @@ public class PaintBundle { mPos++; } + /** + * Set the style STROKE and/or FILL + * @param style + */ public void setStyle(int style) { mArray[mPos] = STYLE | (style << 16); mPos++; } - public void setShader(int shader, String shaderString) { - mArray[mPos] = SHADER | (shader << 16); + /** + * Set the shader id to use + * @param shaderId + */ + public void setShader(int shaderId) { + mArray[mPos] = SHADER; + mPos++; + mArray[mPos] = shaderId; mPos++; } + /** + * Set the Alpha value + */ public void setAlpha(float alpha) { mArray[mPos] = ALPHA; mPos++; @@ -729,7 +783,6 @@ public class PaintBundle { * destination pixels * (content of the render target). * - * * @param blendmode The blend mode to be installed in the paint */ public void setBlendMode(int blendmode) { @@ -825,5 +878,216 @@ public class PaintBundle { return "null"; } -} + /** + * Check all the floats for Nan(id) floats and call listenTo + * @param context + * @param support + */ + public void registerVars(RemoteContext context, VariableSupport support) { + int i = 0; + while (i < mPos) { + int cmd = mArray[i++]; + int type = cmd & 0xFFFF; + switch (type) { + case STROKE_MITER: + case STROKE_WIDTH: + case ALPHA: + case TEXT_SIZE: + float v = Float.intBitsToFloat(mArray[i++]); + if (Float.isNaN(v)) { + context.listensTo(Utils.idFromNan(v), support); + } + break; + case COLOR_ID: + context.listensTo(mArray[i++], support); + break; + case COLOR: + + case TYPEFACE: + case SHADER: + case COLOR_FILTER: + i++; + break; + case STROKE_JOIN: + case FILTER_BITMAP: + case STROKE_CAP: + case STYLE: + case IMAGE_FILTER_QUALITY: + case BLEND_MODE: + case ANTI_ALIAS: + break; + + case GRADIENT: { + // TODO gradients should be handled correctly + i = callPrintGradient(cmd, mArray, i, new StringBuilder()); + } + } + } + } + + /** + * Update variables if any are float ids + * @param context + */ + public void updateVariables(RemoteContext context) { + if (mOutArray == null) { + mOutArray = Arrays.copyOf(mArray, mArray.length); + } else { + System.arraycopy(mArray, 0, mOutArray, 0, mArray.length); + } + int i = 0; + while (i < mPos) { + int cmd = mArray[i++]; + int type = cmd & 0xFFFF; + switch (type) { + case STROKE_MITER: + case STROKE_WIDTH: + case ALPHA: + case TEXT_SIZE: + mOutArray[i] = fixFloatVar(mArray[i], context); + i++; + break; + case COLOR_ID: + mOutArray[i] = fixColor(mArray[i], context); + i++; + break; + case COLOR: + case TYPEFACE: + case SHADER: + case COLOR_FILTER: + i++; + break; + case STROKE_JOIN: + case FILTER_BITMAP: + case STROKE_CAP: + case STYLE: + case IMAGE_FILTER_QUALITY: + case BLEND_MODE: + case ANTI_ALIAS: + break; + + case GRADIENT: { + // TODO gradients should be handled correctly + i = updateFloatsInGradient(cmd, mOutArray, mArray, i, context); + } + } + } + } + + private int fixFloatVar(int val, RemoteContext context) { + float v = Float.intBitsToFloat(val); + if (Float.isNaN(v)) { + int id = Utils.idFromNan(v); + return Float.floatToRawIntBits(context.getFloat(id)); + } + return val; + } + + private int fixColor(int colorId, RemoteContext context) { + int n = context.getColor(colorId); + return n; + } + + int updateFloatsInGradient(int cmd, int[] out, int[] array, + int i, + RemoteContext context) { + int ret = i; + int type = (cmd >> 16); + switch (type) { + case 0: { + int len = array[ret++]; + if (len > 0) { + for (int j = 0; j < len; j++) { + ret++; + } + } + len = array[ret++]; + + if (len > 0) { + for (int j = 0; j < len; j++) { + out[ret] = fixFloatVar(array[ret], context); + ret++; + } + } + + out[ret] = fixFloatVar(array[ret], context); + ret++; + out[ret] = fixFloatVar(array[ret], context); + ret++; + + // end + out[ret] = fixFloatVar(array[ret], context); + ret++; + out[ret] = fixFloatVar(array[ret], context); + ret++; + ret++; // tileMode + } + + break; + case 1: { + // RadialGradient + int len = array[ret++]; + if (len > 0) { + for (int j = 0; j < len; j++) { + ret++; + } + } + len = array[ret++]; + if (len > 0) { + for (int j = 0; j < len; j++) { + out[ret] = fixFloatVar(array[ret], context); + ret++; + } + } + + + // center + out[ret] = fixFloatVar(array[ret], context); + ret++; + out[ret] = fixFloatVar(array[ret], context); + ret++; + // radius + out[ret] = fixFloatVar(array[ret], context); + ret++; + ret++; // tileMode + + } + + break; + case 2: { + // SweepGradient + int len = array[ret++]; + int[] colors = null; + if (len > 0) { + colors = new int[len]; + for (int j = 0; j < colors.length; j++) { + colors[j] = array[ret++]; + + } + } + len = array[ret++]; + float[] stops = null; + if (len > 0) { + stops = new float[len]; + for (int j = 0; j < stops.length; j++) { + out[ret] = fixFloatVar(array[ret], context); + ret++; + } + } + + // center + out[ret] = fixFloatVar(array[ret], context); + ret++; + out[ret] = fixFloatVar(array[ret], context); + ret++; + } + break; + default: { + System.err.println("gradient type unknown"); + } + } + + return ret; + } +}
\ No newline at end of file diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChangeAdapter.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChangeAdapter.java index 994bf6d7e327..28fe63a03c67 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChangeAdapter.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChangeAdapter.java @@ -27,7 +27,6 @@ public class PaintChangeAdapter implements PaintChanges { } - @Override public void setStrokeWidth(float width) { @@ -49,7 +48,7 @@ public class PaintChangeAdapter implements PaintChanges { } @Override - public void setShader(int shader, String shaderString) { + public void setShader(int shader) { } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChanges.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChanges.java index 87e58ac35930..d5dc3889add3 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChanges.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChanges.java @@ -15,9 +15,14 @@ */ package com.android.internal.widget.remotecompose.core.operations.paint; +/** + * Interface to a paint object + * For more details see Android Paint + */ public interface PaintChanges { - + // MASK to be set/cleared + int CLEAR_TEXT_SIZE = 1 << (PaintBundle.TEXT_SIZE - 1); int CLEAR_TEXT_STYLE = 1 << (PaintBundle.TYPEFACE - 1); int CLEAR_COLOR = 1 << (PaintBundle.COLOR - 1); int CLEAR_STROKE_WIDTH = 1 << (PaintBundle.STROKE_WIDTH - 1); @@ -32,21 +37,101 @@ public interface PaintChanges { int CLEAR_COLOR_FILTER = 1 << (PaintBundle.COLOR_FILTER - 1); int VALID_BITS = 0x1FFF; // only the first 13 bit are valid now - + /** + * Set the size of text + * @param size + */ void setTextSize(float size); + + /** + * Set the width of lines + * @param width + */ void setStrokeWidth(float width); + + /** + * Set the color to use + * @param color + */ void setColor(int color); + + /** + * Set the Stroke Cap + * @param cap + */ void setStrokeCap(int cap); + + /** + * Set the Stroke style FILL and/or STROKE + * @param style + */ void setStyle(int style); - void setShader(int shader, String shaderString); + + /** + * Set the id of the shader to use + * @param shader + */ + void setShader(int shader); + + /** + * Set the way image is interpolated + * @param quality + */ void setImageFilterQuality(int quality); + + /** + * Set the alpha to draw under + * @param a + */ void setAlpha(float a); + + /** + * Set the Stroke Miter + * @param miter + */ void setStrokeMiter(float miter); + + /** + * Set the Stroke Join + * @param join + */ void setStrokeJoin(int join); + + /** + * Should bitmaps be interpolated + * @param filter + */ void setFilterBitmap(boolean filter); + + /** + * Set the blend mode can be porterduff + others + * @param mode + */ void setBlendMode(int mode); + + /** + * Set the AntiAlias. Typically true + * Set to off when you need pixilated look (e.g. QR codes) + * @param aa + */ void setAntiAlias(boolean aa); + + /** + * Clear some sub set of the settings + * @param mask + */ void clear(long mask); + + /** + * Set a linear gradient fill + * @param colorsArray + * @param stopsArray + * @param startX + * @param startY + * @param endX + * @param endY + * @param tileMode + */ void setLinearGradient( int[] colorsArray, float[] stopsArray, @@ -57,6 +142,15 @@ public interface PaintChanges { int tileMode ); + /** + * Set a radial gradient fill + * @param colorsArray + * @param stopsArray + * @param centerX + * @param centerY + * @param radius + * @param tileMode + */ void setRadialGradient( int[] colorsArray, float[] stopsArray, @@ -66,6 +160,13 @@ public interface PaintChanges { int tileMode ); + /** + * Set a sweep gradient fill + * @param colorsArray + * @param stopsArray + * @param centerX + * @param centerY + */ void setSweepGradient( int[] colorsArray, float[] stopsArray, @@ -73,9 +174,19 @@ public interface PaintChanges { float centerY ); - + /** + * Set Color filter mod + * @param color + * @param mode + */ void setColorFilter(int color, int mode); + /** + * Set TypeFace 0,1,2 + * TODO above should point to a string to be decoded + * @param fontType + * @param weight + * @param italic + */ void setTypeFace(int fontType, int weight, boolean italic); -} - +}
\ No newline at end of file diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java new file mode 100644 index 000000000000..616048d424ec --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java @@ -0,0 +1,452 @@ +/* + * 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.utilities; + +/** + * high performance floating point expression evaluator used in animation + */ +public class AnimatedFloatExpression { + static IntMap<String> sNames = new IntMap<>(); + public static final int OFFSET = 0x100; + public static final float ADD = asNan(OFFSET + 1); + public static final float SUB = asNan(OFFSET + 2); + public static final float MUL = asNan(OFFSET + 3); + public static final float DIV = asNan(OFFSET + 4); + public static final float MOD = asNan(OFFSET + 5); + public static final float MIN = asNan(OFFSET + 6); + public static final float MAX = asNan(OFFSET + 7); + public static final float POW = asNan(OFFSET + 8); + public static final float SQRT = asNan(OFFSET + 9); + public static final float ABS = asNan(OFFSET + 10); + public static final float SIGN = asNan(OFFSET + 11); + public static final float COPY_SIGN = asNan(OFFSET + 12); + public static final float EXP = asNan(OFFSET + 13); + public static final float FLOOR = asNan(OFFSET + 14); + public static final float LOG = asNan(OFFSET + 15); + public static final float LN = asNan(OFFSET + 16); + public static final float ROUND = asNan(OFFSET + 17); + public static final float SIN = asNan(OFFSET + 18); + public static final float COS = asNan(OFFSET + 19); + public static final float TAN = asNan(OFFSET + 20); + public static final float ASIN = asNan(OFFSET + 21); + public static final float ACOS = asNan(OFFSET + 22); + + public static final float ATAN = asNan(OFFSET + 23); + + public static final float ATAN2 = asNan(OFFSET + 24); + public static final float MAD = asNan(OFFSET + 25); + public static final float IFELSE = asNan(OFFSET + 26); + + public static final float CLAMP = asNan(OFFSET + 27); + public static final float CBRT = asNan(OFFSET + 28); + public static final float DEG = asNan(OFFSET + 29); + public static final float RAD = asNan(OFFSET + 30); + public static final float CEIL = asNan(OFFSET + 31); + + + public static final float LAST_OP = 31; + + + public static final float VAR1 = asNan(OFFSET + 27); + public static final float VAR2 = asNan(OFFSET + 28); + + // TODO CLAMP, CBRT, DEG, RAD, EXPM1, CEIL, FLOOR + private static final float FP_PI = (float) Math.PI; + private static final float FP_TO_RAD = 57.29577951f; // 180/PI + private static final float FP_TO_DEG = 0.01745329252f; // 180/PI + + float[] mStack; + float[] mLocalStack = new float[128]; + float[] mVar; + + /** + * is float a math operator + * @param v + * @return + */ + public static boolean isMathOperator(float v) { + if (Float.isNaN(v)) { + int pos = fromNaN(v); + return pos > OFFSET && pos <= OFFSET + LAST_OP; + } + return false; + } + + interface Op { + int eval(int sp); + } + + /** + * Evaluate a float expression + * @param exp + * @param var + * @return + */ + public float eval(float[] exp, float... var) { + mStack = exp; + mVar = var; + int sp = -1; + for (int i = 0; i < mStack.length; i++) { + float v = mStack[i]; + if (Float.isNaN(v)) { + sp = mOps[fromNaN(v) - OFFSET].eval(sp); + } else { + mStack[++sp] = v; + } + } + return mStack[sp]; + } + + /** + * Evaluate a float expression + * @param exp + * @param len + * @param var + * @return + */ + public float eval(float[] exp, int len, float... var) { + System.arraycopy(exp, 0, mLocalStack, 0, len); + mStack = mLocalStack; + mVar = var; + int sp = -1; + for (int i = 0; i < len; i++) { + float v = mStack[i]; + if (Float.isNaN(v)) { + sp = mOps[fromNaN(v) - OFFSET].eval(sp); + } else { + mStack[++sp] = v; + } + } + return mStack[sp]; + } + + /** + * Evaluate a float expression + * @param exp + * @param var + * @return + */ + public float evalDB(float[] exp, float... var) { + mStack = exp; + mVar = var; + int sp = -1; + for (float v : exp) { + if (Float.isNaN(v)) { + System.out.print(" " + sNames.get((fromNaN(v) - OFFSET))); + sp = mOps[fromNaN(v) - OFFSET].eval(sp); + } else { + System.out.print(" " + v); + mStack[++sp] = v; + } + } + return mStack[sp]; + } + + Op[] mOps = { + null, + (sp) -> { // ADD + mStack[sp - 1] = mStack[sp - 1] + mStack[sp]; + return sp - 1; + }, + (sp) -> { // SUB + mStack[sp - 1] = mStack[sp - 1] - mStack[sp]; + return sp - 1; + }, + (sp) -> { // MUL + mStack[sp - 1] = mStack[sp - 1] * mStack[sp]; + return sp - 1; + }, + (sp) -> { // DIV + mStack[sp - 1] = mStack[sp - 1] / mStack[sp]; + return sp - 1; + }, + (sp) -> { // MOD + mStack[sp - 1] = mStack[sp - 1] % mStack[sp]; + return sp - 1; + }, + (sp) -> { // MIN + mStack[sp - 1] = (float) Math.min(mStack[sp - 1], mStack[sp]); + return sp - 1; + }, + (sp) -> { // MAX + mStack[sp - 1] = (float) Math.max(mStack[sp - 1], mStack[sp]); + return sp - 1; + }, + (sp) -> { // POW + mStack[sp - 1] = (float) Math.pow(mStack[sp - 1], mStack[sp]); + return sp - 1; + }, + (sp) -> { // SQRT + mStack[sp] = (float) Math.sqrt(mStack[sp]); + return sp; + }, + (sp) -> { // ABS + mStack[sp] = (float) Math.abs(mStack[sp]); + return sp; + }, + (sp) -> { // SIGN + mStack[sp] = (float) Math.signum(mStack[sp]); + return sp; + }, + (sp) -> { // copySign + mStack[sp - 1] = (float) Math.copySign(mStack[sp - 1], mStack[sp]); + return sp - 1; + }, + (sp) -> { // EXP + mStack[sp] = (float) Math.exp(mStack[sp]); + return sp; + }, + (sp) -> { // FLOOR + mStack[sp] = (float) Math.floor(mStack[sp]); + return sp; + }, + (sp) -> { // LOG + mStack[sp] = (float) Math.log10(mStack[sp]); + return sp; + }, + (sp) -> { // LN + mStack[sp] = (float) Math.log(mStack[sp]); + return sp; + }, + (sp) -> { // ROUND + mStack[sp] = (float) Math.round(mStack[sp]); + return sp; + }, + (sp) -> { // SIN + mStack[sp] = (float) Math.sin(mStack[sp]); + return sp; + }, + (sp) -> { // COS + mStack[sp] = (float) Math.cos(mStack[sp]); + return sp; + }, + (sp) -> { // TAN + mStack[sp] = (float) Math.tan(mStack[sp]); + return sp; + }, + (sp) -> { // ASIN + mStack[sp] = (float) Math.asin(mStack[sp]); + return sp; + }, + (sp) -> { // ACOS + mStack[sp] = (float) Math.acos(mStack[sp]); + return sp; + }, + (sp) -> { // ATAN + mStack[sp] = (float) Math.atan(mStack[sp]); + return sp; + }, + (sp) -> { // ATAN2 + mStack[sp - 1] = (float) Math.atan2(mStack[sp - 1], mStack[sp]); + return sp - 1; + }, + (sp) -> { // MAD + mStack[sp - 2] = mStack[sp] + mStack[sp - 1] * mStack[sp - 2]; + return sp - 2; + }, + (sp) -> { // Ternary conditional + mStack[sp - 2] = (mStack[sp] > 0) + ? mStack[sp - 1] : mStack[sp - 2]; + return sp - 2; + }, + (sp) -> { // CLAMP(min,max, val) + mStack[sp - 2] = Math.min(Math.max(mStack[sp - 2], mStack[sp]), + mStack[sp - 1]); + return sp - 2; + }, + (sp) -> { // CBRT cuberoot + mStack[sp] = (float) Math.pow(mStack[sp], 1 / 3.); + return sp; + }, + (sp) -> { // DEG + mStack[sp] = mStack[sp] * FP_TO_RAD; + return sp; + }, + (sp) -> { // RAD + mStack[sp] = mStack[sp] * FP_TO_DEG; + return sp; + }, + (sp) -> { // CEIL + mStack[sp] = (float) Math.ceil(mStack[sp]); + return sp; + }, + (sp) -> { // first var = + mStack[sp] = mVar[0]; + return sp; + }, + (sp) -> { // second var y? + mStack[sp] = mVar[1]; + return sp; + }, + (sp) -> { // 3rd var z? + mStack[sp] = mVar[2]; + return sp; + }, + }; + + static { + int k = 0; + sNames.put(k++, "NOP"); + sNames.put(k++, "+"); + sNames.put(k++, "-"); + sNames.put(k++, "*"); + sNames.put(k++, "/"); + sNames.put(k++, "%"); + sNames.put(k++, "min"); + sNames.put(k++, "max"); + sNames.put(k++, "pow"); + sNames.put(k++, "sqrt"); + sNames.put(k++, "abs"); + sNames.put(k++, "sign"); + sNames.put(k++, "copySign"); + sNames.put(k++, "exp"); + sNames.put(k++, "floor"); + sNames.put(k++, "log"); + sNames.put(k++, "ln"); + sNames.put(k++, "round"); + sNames.put(k++, "sin"); + sNames.put(k++, "cos"); + sNames.put(k++, "tan"); + sNames.put(k++, "asin"); + sNames.put(k++, "acos"); + sNames.put(k++, "atan"); + sNames.put(k++, "atan2"); + sNames.put(k++, "mad"); + sNames.put(k++, "ifElse"); + sNames.put(k++, "clamp"); + sNames.put(k++, "cbrt"); + sNames.put(k++, "deg"); + sNames.put(k++, "rad"); + sNames.put(k++, "ceil"); + sNames.put(k++, "a[0]"); + sNames.put(k++, "a[1]"); + sNames.put(k++, "a[2]"); + } + + /** + * given a float command return its math name (e.g sin, cos etc.) + * @param f + * @return + */ + public static String toMathName(float f) { + int id = fromNaN(f) - OFFSET; + return sNames.get(id); + } + + /** + * Convert an expression encoded as an array of floats int ot a string + * @param exp + * @param labels + * @return + */ + public static String toString(float[] exp, String[] labels) { + StringBuilder s = new StringBuilder(); + for (int i = 0; i < exp.length; i++) { + float v = exp[i]; + if (Float.isNaN(v)) { + if (isMathOperator(v)) { + s.append(toMathName(v)); + } else { + s.append("["); + s.append(fromNaN(v)); + s.append("]"); + } + } else { + if (labels[i] != null) { + s.append(labels[i]); + } + s.append(v); + } + s.append(" "); + } + return s.toString(); + } + + static String toString(float[] exp, int sp) { + String[] str = new String[exp.length]; + if (Float.isNaN(exp[sp])) { + int id = fromNaN(exp[sp]) - OFFSET; + switch (NO_OF_OPS[id]) { + case -1: + return "nop"; + case 1: + return sNames.get(id) + "(" + toString(exp, sp + 1) + ") "; + case 2: + if (infix(id)) { + return "(" + toString(exp, sp + 1) + + sNames.get(id) + " " + + toString(exp, sp + 2) + ") "; + } else { + return sNames.get(id) + "(" + + toString(exp, sp + 1) + ", " + + toString(exp, sp + 2) + ")"; + } + case 3: + if (infix(id)) { + return "((" + toString(exp, sp + 1) + ") ? " + + toString(exp, sp + 2) + ":" + + toString(exp, sp + 3) + ")"; + } else { + return sNames.get(id) + + "(" + toString(exp, sp + 1) + + ", " + toString(exp, sp + 2) + + ", " + toString(exp, sp + 3) + ")"; + } + } + } + return Float.toString(exp[sp]); + } + + static final int[] NO_OF_OPS = { + -1, // no op + 2, 2, 2, 2, 2, // + - * / % + 2, 2, 2, // min max, power + 1, 1, 1, 1, 1, 1, 1, 1, //sqrt,abs,CopySign,exp,floor,log,ln + 1, 1, 1, 1, 1, 1, 1, 2, // round,sin,cos,tan,asin,acos,atan,atan2 + 3, 3, 3, 1, 1, 1, 1, + 0, 0, 0 // mad, ?:, + // a[0],a[1],a[2] + }; + + /** + * to be used by parser to determine if command is infix + * @param n + * @return + */ + static boolean infix(int n) { + return ((n < 6) || (n == 25) || (n == 26)); + } + + /** + * Convert an id into a NaN object + * @param v + * @return + */ + public static float asNan(int v) { + return Float.intBitsToFloat(v | -0x800000); + } + + /** + * Get ID from a NaN float + * @param v + * @return + */ + public static int fromNaN(float v) { + int b = Float.floatToRawIntBits(v); + return b & 0xFFFFF; + } + +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ColorUtils.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ColorUtils.java new file mode 100644 index 000000000000..0ea28a8bb900 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ColorUtils.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations.utilities; + +/** + * These are tools to use long Color as variables + * long colors are stored a 0xXXXXXXXX XXXXXX?? + * in SRGB the colors are stored 0xAARRGGBB,00000000 + * SRGB color sapce is color space 0 + * Our Color will use color float with a + * Current android supports + * SRGB, LINEAR_SRGB, EXTENDED_SRGB, LINEAR_EXTENDED_SRGB, BT709, BT2020, + * DCI_P3, DISPLAY_P3, NTSC_1953, SMPTE_C, ADOBE_RGB, PRO_PHOTO_RGB, ACES, + * ACESCG, CIE_XYZ, CIE_LAB, BT2020_HLG, BT2020_PQ 0..17 respectively + * + * Our color space will be 62 (MAX_ID-1). (0x3E) + * Storing the default value in SRGB format and having the + * id of the color between the ARGB values and the 62 i.e. + * 0xAARRGGBB 00 00 00 3E + * + */ +public class ColorUtils { + public static int RC_COLOR = 62; + + long packRCColor(int defaultARGB, int id) { + long l = defaultARGB; + return (l << 32) | id << 8 | RC_COLOR; + } + + boolean isRCColor(long color) { + return ((color & 0x3F) == 62); + } + + int getID(long color) { + if (isRCColor(color)) { + return (int) ((color & 0xFFFFFF00) >> 8); + } + return -1; + } + + /** + * get default color from long color + * @param color + * @return + */ + public int getDefaultColor(long color) { + if (isRCColor(color)) { + return (int) (color >> 32); + } + if (((color & 0xFF) == 0)) { + return (int) (color >> 32); + } + return 0; + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java index 8051ef1ab37c..0512fa6be710 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java @@ -50,7 +50,6 @@ public class IntMap<T> { return insert(key, value); } - public T get(int key) { int index = findKey(key); if (index == -1) { diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/NanMap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/NanMap.java new file mode 100644 index 000000000000..f4cd504d650f --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/NanMap.java @@ -0,0 +1,75 @@ +/* + * 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.utilities; + +import com.android.internal.widget.remotecompose.core.operations.Utils; + +/** + * This defines the major id maps and ranges used by remote compose + * Generally ids ranging from 0 ... FFF (4095) are for ids + * 0x1000-0x1100 are used for path operations in PathData + * 0x1100-0x1200 are used for math operations in Animated float + * 0x + */ +public class NanMap { + + public static final int MOVE = 0x1000; + public static final int LINE = 0x1001; + public static final int QUADRATIC = 0x1002; + public static final int CONIC = 0x1003; + public static final int CUBIC = 0x1004; + public static final int CLOSE = 0x1005; + public static final int DONE = 0x1006; + public static final float MOVE_NAN = Utils.asNan(MOVE); + public static final float LINE_NAN = Utils.asNan(LINE); + public static final float QUADRATIC_NAN = Utils.asNan(QUADRATIC); + public static final float CONIC_NAN = Utils.asNan(CONIC); + public static final float CUBIC_NAN = Utils.asNan(CUBIC); + public static final float CLOSE_NAN = Utils.asNan(CLOSE); + public static final float DONE_NAN = Utils.asNan(DONE); + + /** + * + */ + public static final float ADD = asNan(0x1100); + public static final float SUB = asNan(0x1101); + public static final float MUL = asNan(0x1102); + public static final float DIV = asNan(0x1103); + public static final float MOD = asNan(0x1104); + public static final float MIN = asNan(0x1105); + public static final float MAX = asNan(0x1106); + public static final float POW = asNan(0x1107); + + + /** + * Get ID from Nan float + * @param v + * @return + */ + public static int fromNaN(float v) { + int b = Float.floatToRawIntBits(v); + return b & 0xFFFFF; + } + + /** + * Given id return as a Nan float + * @param v + * @return + */ + public static float asNan(int v) { + return Float.intBitsToFloat(v | 0xFF800000); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringUtils.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringUtils.java new file mode 100644 index 000000000000..8dd5405ddf12 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringUtils.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations.utilities; + +import java.util.Arrays; + +/** + * Utilities for string manipulation + */ +public class StringUtils { + /** + * Converts a float into a string. + * Providing a defined number of characters before and after the + * decimal point. + * + * @param value The value to convert to string + * @param beforeDecimalPoint digits before the decimal point + * @param afterDecimalPoint digits after the decimal point + * @param pre character to pad width 0 = no pad typically ' ' or '0' + * @param post character to pad width 0 = no pad typically ' ' or '0' + * @return + */ + public static String floatToString(float value, + int beforeDecimalPoint, + int afterDecimalPoint, + char pre, char post) { + + int integerPart = (int) value; + float fractionalPart = value % 1; + + // Convert integer part to string and pad with spaces + String integerPartString = String.valueOf(integerPart); + int iLen = integerPartString.length(); + if (iLen < beforeDecimalPoint) { + int spacesToPad = beforeDecimalPoint - iLen; + if (pre != 0) { + char[] pad = new char[spacesToPad]; + Arrays.fill(pad, pre); + integerPartString = new String(pad) + integerPartString; + } + + + } else if (iLen > beforeDecimalPoint) { + integerPartString = integerPartString.substring(iLen - beforeDecimalPoint); + } + if (afterDecimalPoint == 0) { + return integerPartString; + } + // Convert fractional part to string and pad with zeros + + for (int i = 0; i < afterDecimalPoint; i++) { + fractionalPart *= 10; + } + + fractionalPart = Math.round(fractionalPart); + + for (int i = 0; i < afterDecimalPoint; i++) { + fractionalPart *= .1; + } + + String fact = Float.toString(fractionalPart); + fact = fact.substring(2, Math.min(fact.length(), afterDecimalPoint + 2)); + int trim = fact.length(); + for (int i = fact.length() - 1; i >= 0; i--) { + if (fact.charAt(i) != '0') { + break; + } + trim--; + } + if (trim != fact.length()) { + fact = fact.substring(0, trim); + } + int len = fact.length(); + if (post != 0 && len < afterDecimalPoint) { + char[] c = new char[afterDecimalPoint - len]; + Arrays.fill(c, post); + fact = fact + new String(c); + } + + return integerPartString + "." + fact; + } + +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/BounceCurve.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/BounceCurve.java new file mode 100644 index 000000000000..c3cd5ae9c79d --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/BounceCurve.java @@ -0,0 +1,67 @@ +/* + * 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.utilities.easing; + +/** + * Provide a specific bouncing easing function + */ +public class BounceCurve extends Easing { + private static final float N1 = 7.5625f; + private static final float D1 = 2.75f; + + BounceCurve(int type) { + mType = type; + } + + @Override + public float get(float x) { + float t = x; + if (t < 0) { + return 0f; + } + if (t < 1 / D1) { + return 1 / (1 + 1 / D1) * (N1 * t * t + t); + } else if (t < 2 / D1) { + t -= 1.5f / D1; + return N1 * t * t + 0.75f; + } else if (t < 2.5 / D1) { + t -= 2.25f / D1; + return N1 * t * t + 0.9375f; + } else if (t <= 1) { + t -= 2.625f / D1; + return N1 * t * t + 0.984375f; + } + return 1f; + } + + @Override + public float getDiff(float x) { + if (x < 0) { + return 0f; + } + if (x < 1 / D1) { + return 2 * N1 * x / (1 + 1 / D1) + 1 / (1 + 1 / D1); + } else if (x < 2 / D1) { + return 2 * N1 * (x - 1.5f / D1); + } else if (x < 2.5 / D1) { + return 2 * N1 * (x - 2.25f / D1); + } else if (x <= 1) { + return 2 * N1 * (x - 2.625f / D1); + } + return 0f; + } + +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/CubicEasing.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/CubicEasing.java new file mode 100644 index 000000000000..fd1ee036e475 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/CubicEasing.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations.utilities.easing; + +class CubicEasing extends Easing { + float mType = 0; + float mX1 = 0f; + float mY1 = 0f; + float mX2 = 0f; + float mY2 = 0f; + + private static final float[] STANDARD = {0.4f, 0.0f, 0.2f, 1f}; + private static final float[] ACCELERATE = {0.4f, 0.05f, 0.8f, 0.7f}; + private static final float[] DECELERATE = {0.0f, 0.0f, 0.2f, 0.95f}; + private static final float[] LINEAR = {1f, 1f, 0f, 0f}; + private static final float[] ANTICIPATE = {0.36f, 0f, 0.66f, -0.56f}; + private static final float[] OVERSHOOT = {0.34f, 1.56f, 0.64f, 1f}; + + CubicEasing(int type) { + mType = type; + config(type); + } + + CubicEasing(float x1, float y1, float x2, float y2) { + setup(x1, y1, x2, y2); + } + + public void config(int type) { + + switch (type) { + case CUBIC_STANDARD: + setup(STANDARD); + break; + case CUBIC_ACCELERATE: + setup(ACCELERATE); + break; + case CUBIC_DECELERATE: + setup(DECELERATE); + break; + case CUBIC_LINEAR: + setup(LINEAR); + break; + case CUBIC_ANTICIPATE: + setup(ANTICIPATE); + break; + case CUBIC_OVERSHOOT: + setup(OVERSHOOT); + break; + } + mType = type; + } + + void setup(float[] values) { + setup(values[0], values[1], values[2], values[3]); + } + + void setup(float x1, float y1, float x2, float y2) { + mX1 = x1; + mY1 = y1; + mX2 = x2; + mY2 = y2; + } + + private float getX(float t) { + float t1 = 1 - t; + // no need for because start at 0,0 float f0 = (1 - t) * (1 - t) * (1 - t) + float f1 = 3 * t1 * t1 * t; + float f2 = 3 * t1 * t * t; + float f3 = t * t * t; + return mX1 * f1 + mX2 * f2 + f3; + } + + private float getY(float t) { + float t1 = 1 - t; + // no need for testing because start at 0,0 float f0 = (1 - t) * (1 - t) * (1 - t) + float f1 = 3 * t1 * t1 * t; + float f2 = 3 * t1 * t * t; + float f3 = t * t * t; + return mY1 * f1 + mY2 * f2 + f3; + } + + private float getDiffX(float t) { + float t1 = 1 - t; + return 3 * t1 * t1 * mX1 + 6 * t1 * t * (mX2 - mX1) + 3 * t * t * (1 - mX2); + } + + private float getDiffY(float t) { + float t1 = 1 - t; + return 3 * t1 * t1 * mY1 + 6 * t1 * t * (mY2 - mY1) + 3 * t * t * (1 - mY2); + } + + /** + * binary search for the region and linear interpolate the answer + */ + public float getDiff(float x) { + float t = 0.5f; + float range = 0.5f; + while (range > D_ERROR) { + float tx = getX(t); + range *= 0.5; + if (tx < x) { + t += range; + } else { + t -= range; + } + } + float x1 = getX(t - range); + float x2 = getX(t + range); + float y1 = getY(t - range); + float y2 = getY(t + range); + return (y2 - y1) / (x2 - x1); + } + + /** + * binary search for the region and linear interpolate the answer + */ + public float get(float x) { + if (x <= 0.0f) { + return 0f; + } + if (x >= 1.0f) { + return 1.0f; + } + float t = 0.5f; + float range = 0.5f; + while (range > ERROR) { + float tx = getX(t); + range *= 0.5f; + if (tx < x) { + t += range; + } else { + t -= range; + } + } + float x1 = getX(t - range); + float x2 = getX(t + range); + float y1 = getY(t - range); + float y2 = getY(t + range); + return (y2 - y1) * (x - x1) / (x2 - x1) + y1; + } + + private static final float ERROR = 0.01f; + private static final float D_ERROR = 0.0001f; +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/Easing.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/Easing.java new file mode 100644 index 000000000000..4ed955069d78 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/Easing.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations.utilities.easing; + +/** + * The standard interface to Easing functions + */ +public abstract class Easing { + int mType; + /** + * get the value at point x + */ + public abstract float get(float x); + + /** + * get the slope of the easing function at at x + */ + public abstract float getDiff(float x); + + public int getType() { + return mType; + } + + public static final int CUBIC_STANDARD = 1; + public static final int CUBIC_ACCELERATE = 2; + public static final int CUBIC_DECELERATE = 3; + public static final int CUBIC_LINEAR = 4; + public static final int CUBIC_ANTICIPATE = 5; + public static final int CUBIC_OVERSHOOT = 6; + public static final int CUBIC_CUSTOM = 11; + public static final int SPLINE_CUSTOM = 12; + public static final int EASE_OUT_BOUNCE = 13; + public static final int EASE_OUT_ELASTIC = 14; + +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/ElasticOutCurve.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/ElasticOutCurve.java new file mode 100644 index 000000000000..e26958302e3c --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/ElasticOutCurve.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations.utilities.easing; + +/** + * Provide a bouncing Easing function + */ +public class ElasticOutCurve extends Easing { + private static final float F_PI = (float) Math.PI; + private static final float C4 = 2 * F_PI / 3; + private static final float TWENTY_PI = 20 * F_PI; + private static final float LOG_8 = (float) Math.log(8.0f); + + @Override + public float get(float x) { + if (x <= 0) { + return 0.0f; + } + if (x >= 1) { + return 1.0f; + } else + return (float) (Math.pow(2.0f, -10 * x) + * Math.sin((x * 10 - 0.75f) * C4) + 1); + } + + @Override + public float getDiff(float x) { + if (x < 0 || x > 1) { + return 0.0f; + } else + return (float) ((5 * Math.pow(2.0f, (1 - 10 * x)) + * (LOG_8 * Math.cos(TWENTY_PI * x / 3) + 2 + * F_PI * Math.sin(TWENTY_PI * x / 3)) + / 3)); + } +} 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 new file mode 100644 index 000000000000..4f484de1045e --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations.utilities.easing; + +/** + * Support Animation of the FloatExpression + */ +public class FloatAnimation extends Easing { + float[] mSpec; + // mSpec[0] = duration + // int(mSpec[1]) = num_of_param << 16 | type + // mSpec[2..1+num_of_param] params + // mSpec[2+num_of_param] starting Value + Easing mEasingCurve; + private int mType = CUBIC_STANDARD; + private float mDuration = 1; + private float mWrap = Float.NaN; + private float mInitialValue = Float.NaN; + private float mTargetValue = Float.NaN; + private float mScale = 1; + float mOffset = 0; + + @Override + public String toString() { + + String str = "type " + mType; + if (!Float.isNaN(mInitialValue)) { + str += " " + mInitialValue; + } + if (!Float.isNaN(mTargetValue)) { + str += " -> " + mTargetValue; + } + if (!Float.isNaN(mWrap)) { + str += " % " + mWrap; + } + + return str; + } + + public FloatAnimation() { + } + + public FloatAnimation(float... description) { + setAnimationDescription(description); + } + + public FloatAnimation(int type, + float duration, + float[] description, + float initialValue, + float wrap) { + setAnimationDescription(packToFloatArray(duration, + type, description, initialValue, wrap)); + } + + /** + * packs spec into a float array + * + * @param duration + * @param type + * @param spec + * @param initialValue + * @return + */ + public static float[] packToFloatArray(float duration, + int type, + float[] spec, + float initialValue, + float wrap) { + int count = 0; + + if (!Float.isNaN(initialValue)) { + count++; + } + if (spec != null) { + count++; + } + if (spec != null || type != CUBIC_STANDARD) { + count++; + count += (spec == null) ? 0 : spec.length; + } + if (duration != 1 || count > 0) { + count++; + } + if (!Float.isNaN(initialValue)) { + count++; + } + if (!Float.isNaN(wrap)) { + count++; + } + float[] ret = new float[count]; + int pos = 0; + int specLen = (spec == null) ? 0 : spec.length; + + if (ret.length > 0) { + ret[pos++] = duration; + + } + if (ret.length > 1) { + int wrapBit = (Float.isNaN(wrap)) ? 0 : 1; + int initBit = (Float.isNaN(initialValue)) ? 0 : 2; + int bits = type | ((wrapBit | initBit) << 8); + ret[pos++] = Float.intBitsToFloat(specLen << 16 | bits); + } + + if (specLen > 0) { + System.arraycopy(spec, 0, ret, pos, spec.length); + pos += spec.length; + } + if (!Float.isNaN(initialValue)) { + ret[pos++] = initialValue; + } + if (!Float.isNaN(wrap)) { + ret[pos] = wrap; + } + return ret; + } + + /** + * Create an animation based on a float encoding of the animation + * @param description + */ + public void setAnimationDescription(float[] description) { + mSpec = description; + mDuration = (mSpec.length == 0) ? 1 : mSpec[0]; + int len = 0; + if (mSpec.length > 1) { + int num_type = Float.floatToRawIntBits(mSpec[1]); + mType = num_type & 0xFF; + boolean wrap = ((num_type >> 8) & 0x1) > 0; + boolean init = ((num_type >> 8) & 0x2) > 0; + len = (num_type >> 16) & 0xFFFF; + int off = 2 + len; + if (init) { + mInitialValue = mSpec[off++]; + } + if (wrap) { + mWrap = mSpec[off]; + } + } + create(mType, description, 2, len); + } + + private void create(int type, float[] params, int offset, int len) { + switch (type) { + case CUBIC_STANDARD: + case CUBIC_ACCELERATE: + case CUBIC_DECELERATE: + case CUBIC_LINEAR: + case CUBIC_ANTICIPATE: + case CUBIC_OVERSHOOT: + mEasingCurve = new CubicEasing(type); + break; + case CUBIC_CUSTOM: + mEasingCurve = new CubicEasing(params[offset + 0], + params[offset + 1], + params[offset + 2], + params[offset + 3] + ); + break; + case EASE_OUT_BOUNCE: + mEasingCurve = new BounceCurve(type); + break; + case EASE_OUT_ELASTIC: + mEasingCurve = new ElasticOutCurve(); + break; + case SPLINE_CUSTOM: + mEasingCurve = new StepCurve(params, offset, len); + break; + } + } + + /** + * Get the duration the interpolate is to take + * @return duration in seconds + */ + public float getDuration() { + return mDuration; + } + + /** + * Set the initial Value + * @param value + */ + public void setInitialValue(float value) { + + if (Float.isNaN(mWrap)) { + mInitialValue = value; + } else { + mInitialValue = value % mWrap; + } + setScaleOffset(); + } + + /** + * Set the target value to interpolate to + * @param value + */ + public void setTargetValue(float value) { + if (Float.isNaN(mWrap)) { + mTargetValue = value; + } else { + if (Math.abs((value % mWrap) + mWrap - mInitialValue) + < Math.abs((value % mWrap) - mInitialValue)) { + mTargetValue = (value % mWrap) + mWrap; + + } else { + mTargetValue = value % mWrap; + } + } + setScaleOffset(); + } + + public float getTargetValue() { + return mTargetValue; + } + + private void setScaleOffset() { + if (!Float.isNaN(mInitialValue) && !Float.isNaN(mTargetValue)) { + mScale = (mTargetValue - mInitialValue); + mOffset = mInitialValue; + } else { + mScale = 1; + mOffset = 0; + } + } + + /** + * get the value at time t in seconds since start + */ + public float get(float t) { + return mEasingCurve.get(t / mDuration) + * (mTargetValue - mInitialValue) + mInitialValue; + } + + /** + * get the slope of the easing function at at x + */ + public float getDiff(float t) { + return mEasingCurve.getDiff(t / mDuration) * (mTargetValue - mInitialValue); + } + + public float getInitialValue() { + return mInitialValue; + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java new file mode 100644 index 000000000000..693deafc5b00 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java @@ -0,0 +1,81 @@ +/* + * 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.utilities.easing; + +/** + * Provides and interface to create easing functions + */ +public class GeneralEasing extends Easing{ + float[] mEasingData = new float[0]; + Easing mEasingCurve = new CubicEasing(CUBIC_STANDARD); + + /** + * Set the curve based on the float encoding of it + * @param data + */ + public void setCurveSpecification(float[] data) { + mEasingData = data; + createEngine(); + } + + public float[] getCurveSpecification() { + return mEasingData; + } + + void createEngine() { + int type = Float.floatToRawIntBits(mEasingData[0]); + switch (type) { + case CUBIC_STANDARD: + case CUBIC_ACCELERATE: + case CUBIC_DECELERATE: + case CUBIC_LINEAR: + case CUBIC_ANTICIPATE: + case CUBIC_OVERSHOOT: + mEasingCurve = new CubicEasing(type); + break; + case CUBIC_CUSTOM: + mEasingCurve = new CubicEasing(mEasingData[1], + mEasingData[2], + mEasingData[3], + mEasingData[5] + ); + break; + case EASE_OUT_BOUNCE: + mEasingCurve = new BounceCurve(type); + break; + } + } + + /** + * get the value at point x + */ + public float get(float x) { + return mEasingCurve.get(x); + } + + /** + * get the slope of the easing function at at x + */ + public float getDiff(float x) { + return mEasingCurve.getDiff(x); + } + + public int getType() { + return mEasingCurve.getType(); + } + + +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java new file mode 100644 index 000000000000..23930b92a282 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java @@ -0,0 +1,370 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations.utilities.easing; + +import java.util.Arrays; + +/** + * This performs a spline interpolation in multiple dimensions + * + * + */ +public class MonotonicCurveFit { + private static final String TAG = "MonotonicCurveFit"; + private double[] mT; + private double[][] mY; + private double[][] mTangent; + private boolean mExtrapolate = true; + double[] mSlopeTemp; + + /** + * create a collection of curves + * @param time the point along the curve + * @param y the parameter at those points + */ + public MonotonicCurveFit(double[] time, double[][] y) { + final int n = time.length; + final int dim = y[0].length; + mSlopeTemp = new double[dim]; + double[][] slope = new double[n - 1][dim]; // could optimize this out + double[][] tangent = new double[n][dim]; + for (int j = 0; j < dim; j++) { + for (int i = 0; i < n - 1; i++) { + double dt = time[i + 1] - time[i]; + slope[i][j] = (y[i + 1][j] - y[i][j]) / dt; + if (i == 0) { + tangent[i][j] = slope[i][j]; + } else { + tangent[i][j] = (slope[i - 1][j] + slope[i][j]) * 0.5f; + } + } + tangent[n - 1][j] = slope[n - 2][j]; + } + + for (int i = 0; i < n - 1; i++) { + for (int j = 0; j < dim; j++) { + if (slope[i][j] == 0.) { + tangent[i][j] = 0.; + tangent[i + 1][j] = 0.; + } else { + double a = tangent[i][j] / slope[i][j]; + double b = tangent[i + 1][j] / slope[i][j]; + double h = Math.hypot(a, b); + if (h > 9.0) { + double t = 3. / h; + tangent[i][j] = t * a * slope[i][j]; + tangent[i + 1][j] = t * b * slope[i][j]; + } + } + } + } + mT = time; + mY = y; + mTangent = tangent; + } + + /** + * Get the position of all curves at time t + * @param t + * @param v + */ + public void getPos(double t, double[] v) { + final int n = mT.length; + final int dim = mY[0].length; + if (mExtrapolate) { + if (t <= mT[0]) { + getSlope(mT[0], mSlopeTemp); + for (int j = 0; j < dim; j++) { + v[j] = mY[0][j] + (t - mT[0]) * mSlopeTemp[j]; + } + return; + } + if (t >= mT[n - 1]) { + getSlope(mT[n - 1], mSlopeTemp); + for (int j = 0; j < dim; j++) { + v[j] = mY[n - 1][j] + (t - mT[n - 1]) * mSlopeTemp[j]; + } + return; + } + } else { + if (t <= mT[0]) { + for (int j = 0; j < dim; j++) { + v[j] = mY[0][j]; + } + return; + } + if (t >= mT[n - 1]) { + for (int j = 0; j < dim; j++) { + v[j] = mY[n - 1][j]; + } + return; + } + } + + for (int i = 0; i < n - 1; i++) { + if (t == mT[i]) { + for (int j = 0; j < dim; j++) { + v[j] = mY[i][j]; + } + } + if (t < mT[i + 1]) { + double h = mT[i + 1] - mT[i]; + double x = (t - mT[i]) / h; + for (int j = 0; j < dim; j++) { + double y1 = mY[i][j]; + double y2 = mY[i + 1][j]; + double t1 = mTangent[i][j]; + double t2 = mTangent[i + 1][j]; + v[j] = interpolate(h, x, y1, y2, t1, t2); + } + return; + } + } + } + + /** + * Get the position of all curves at time t + * @param t + * @param v + */ + public void getPos(double t, float[] v) { + final int n = mT.length; + final int dim = mY[0].length; + if (mExtrapolate) { + if (t <= mT[0]) { + getSlope(mT[0], mSlopeTemp); + for (int j = 0; j < dim; j++) { + v[j] = (float) (mY[0][j] + (t - mT[0]) * mSlopeTemp[j]); + } + return; + } + if (t >= mT[n - 1]) { + getSlope(mT[n - 1], mSlopeTemp); + for (int j = 0; j < dim; j++) { + v[j] = (float) (mY[n - 1][j] + (t - mT[n - 1]) * mSlopeTemp[j]); + } + return; + } + } else { + if (t <= mT[0]) { + for (int j = 0; j < dim; j++) { + v[j] = (float) mY[0][j]; + } + return; + } + if (t >= mT[n - 1]) { + for (int j = 0; j < dim; j++) { + v[j] = (float) mY[n - 1][j]; + } + return; + } + } + + for (int i = 0; i < n - 1; i++) { + if (t == mT[i]) { + for (int j = 0; j < dim; j++) { + v[j] = (float) mY[i][j]; + } + } + if (t < mT[i + 1]) { + double h = mT[i + 1] - mT[i]; + double x = (t - mT[i]) / h; + for (int j = 0; j < dim; j++) { + double y1 = mY[i][j]; + double y2 = mY[i + 1][j]; + double t1 = mTangent[i][j]; + double t2 = mTangent[i + 1][j]; + v[j] = (float) interpolate(h, x, y1, y2, t1, t2); + } + return; + } + } + } + + /** + * Get the position of the jth curve at time t + * @param t + * @param j + * @return + */ + public double getPos(double t, int j) { + final int n = mT.length; + if (mExtrapolate) { + if (t <= mT[0]) { + return mY[0][j] + (t - mT[0]) * getSlope(mT[0], j); + } + if (t >= mT[n - 1]) { + return mY[n - 1][j] + (t - mT[n - 1]) * getSlope(mT[n - 1], j); + } + } else { + if (t <= mT[0]) { + return mY[0][j]; + } + if (t >= mT[n - 1]) { + return mY[n - 1][j]; + } + } + + for (int i = 0; i < n - 1; i++) { + if (t == mT[i]) { + return mY[i][j]; + } + if (t < mT[i + 1]) { + double h = mT[i + 1] - mT[i]; + double x = (t - mT[i]) / h; + double y1 = mY[i][j]; + double y2 = mY[i + 1][j]; + double t1 = mTangent[i][j]; + double t2 = mTangent[i + 1][j]; + return interpolate(h, x, y1, y2, t1, t2); + + } + } + return 0; // should never reach here + } + + /** + * Get the slope of all the curves at position t + * @param t + * @param v + */ + public void getSlope(double t, double[] v) { + final int n = mT.length; + int dim = mY[0].length; + if (t <= mT[0]) { + t = mT[0]; + } else if (t >= mT[n - 1]) { + t = mT[n - 1]; + } + + for (int i = 0; i < n - 1; i++) { + if (t <= mT[i + 1]) { + double h = mT[i + 1] - mT[i]; + double x = (t - mT[i]) / h; + for (int j = 0; j < dim; j++) { + double y1 = mY[i][j]; + double y2 = mY[i + 1][j]; + double t1 = mTangent[i][j]; + double t2 = mTangent[i + 1][j]; + v[j] = diff(h, x, y1, y2, t1, t2) / h; + } + break; + } + } + return; + } + + /** + * Get the slope of the j curve at position t + * @param t + * @param j + * @return + */ + public double getSlope(double t, int j) { + final int n = mT.length; + + if (t < mT[0]) { + t = mT[0]; + } else if (t >= mT[n - 1]) { + t = mT[n - 1]; + } + for (int i = 0; i < n - 1; i++) { + if (t <= mT[i + 1]) { + double h = mT[i + 1] - mT[i]; + double x = (t - mT[i]) / h; + double y1 = mY[i][j]; + double y2 = mY[i + 1][j]; + double t1 = mTangent[i][j]; + double t2 = mTangent[i + 1][j]; + return diff(h, x, y1, y2, t1, t2) / h; + } + } + return 0; // should never reach here + } + + public double[] getTimePoints() { + return mT; + } + + /** + * Cubic Hermite spline + */ + private static double interpolate(double h, + double x, + double y1, + double y2, + double t1, + double t2) { + double x2 = x * x; + double x3 = x2 * x; + return -2 * x3 * y2 + 3 * x2 * y2 + 2 * x3 * y1 - 3 * x2 * y1 + y1 + + h * t2 * x3 + h * t1 * x3 - h * t2 * x2 - 2 * h * t1 * x2 + + h * t1 * x; + } + + /** + * Cubic Hermite spline slope differentiated + */ + private static double diff(double h, double x, double y1, double y2, double t1, double t2) { + double x2 = x * x; + return -6 * x2 * y2 + 6 * x * y2 + 6 * x2 * y1 - 6 * x * y1 + 3 * h * t2 * x2 + + 3 * h * t1 * x2 - 2 * h * t2 * x - 4 * h * t1 * x + h * t1; + } + + /** + * This builds a monotonic spline to be used as a wave function + */ + public static MonotonicCurveFit buildWave(String configString) { + // done this way for efficiency + String str = configString; + double[] values = new double[str.length() / 2]; + int start = configString.indexOf('(') + 1; + int off1 = configString.indexOf(',', start); + int count = 0; + while (off1 != -1) { + String tmp = configString.substring(start, off1).trim(); + values[count++] = Double.parseDouble(tmp); + off1 = configString.indexOf(',', start = off1 + 1); + } + off1 = configString.indexOf(')', start); + String tmp = configString.substring(start, off1).trim(); + values[count++] = Double.parseDouble(tmp); + + return buildWave(Arrays.copyOf(values, count)); + } + + private static MonotonicCurveFit buildWave(double[] values) { + int length = values.length * 3 - 2; + int len = values.length - 1; + double gap = 1.0 / len; + double[][] points = new double[length][1]; + double[] time = new double[length]; + for (int i = 0; i < values.length; i++) { + double v = values[i]; + points[i + len][0] = v; + time[i + len] = i * gap; + if (i > 0) { + points[i + len * 2][0] = v + 1; + time[i + len * 2] = i * gap + 1; + + points[i - 1][0] = v - 1 - gap; + time[i - 1] = i * gap + -1 - gap; + } + } + + return new MonotonicCurveFit(time, points); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java new file mode 100644 index 000000000000..6ed6548405d2 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations.utilities.easing; + + +/** + * This class translates a series of floating point values into a continuous + * curve for use in an easing function including quantize functions + * it is used with the "spline(0,0.3,0.3,0.5,...0.9,1)" it should start at 0 and end at one 1 + */ +public class StepCurve extends Easing { + private static final boolean DEBUG = false; + MonotonicCurveFit mCurveFit; + + public StepCurve(float[] params, int offset, int len) { + mCurveFit = genSpline(params, offset, len); + } + + private static MonotonicCurveFit genSpline(float[] values, int off, int arrayLen) { + int length = arrayLen * 3 - 2; + int len = arrayLen - 1; + double gap = 1.0 / len; + double[][] points = new double[length][1]; + double[] time = new double[length]; + for (int i = 0; i < arrayLen; i++) { + double v = values[i + off]; + points[i + len][0] = v; + time[i + len] = i * gap; + if (i > 0) { + points[i + len * 2][0] = v + 1; + time[i + len * 2] = i * gap + 1; + + points[i - 1][0] = v - 1 - gap; + time[i - 1] = i * gap + -1 - gap; + } + } + + MonotonicCurveFit ms = new MonotonicCurveFit(time, points); + + return ms; + } + + @Override + public float getDiff(float x) { + return (float) mCurveFit.getSlope(x, 0); + } + + + @Override + public float get(float x) { + return (float) mCurveFit.getPos(x, 0); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java index bcda27a40f64..cb0d62ed12e0 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java +++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java @@ -79,6 +79,13 @@ public class RemoteComposeDocument { } /** + * The delay in milliseconds to next repaint -1 = not needed 0 = asap + * @return delay in milliseconds to next repaint or -1 + */ + public int needsRepaint() { + return mDocument.needsRepaint(); + } + /** * Returns true if the document can be displayed given this version of the player * * @param majorVersion the max major version supported by the player @@ -89,5 +96,11 @@ public class RemoteComposeDocument { return mDocument.canBeDisplayed(majorVersion, minorVersion, capabilities); } + @Override + public String toString() { + return "Document{\n" + + mDocument + + '}'; + } } 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 7423a1679d36..cc1f3ddcc093 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java +++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java @@ -16,7 +16,6 @@ package com.android.internal.widget.remotecompose.player; import android.content.Context; -import android.graphics.Color; import android.util.AttributeSet; import android.util.Log; import android.view.ViewGroup; @@ -98,7 +97,6 @@ public class RemoteComposePlayer extends FrameLayout { LayoutParams.MATCH_PARENT); HorizontalScrollView horizontalScrollView = new HorizontalScrollView(getContext()); - horizontalScrollView.setBackgroundColor(Color.TRANSPARENT); horizontalScrollView.setFillViewport(true); horizontalScrollView.addView(mInner, layoutParamsInner); LayoutParams layoutParams = new LayoutParams( @@ -115,7 +113,6 @@ public class RemoteComposePlayer extends FrameLayout { LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); ScrollView scrollView = new ScrollView(getContext()); - scrollView.setBackgroundColor(Color.TRANSPARENT); scrollView.setFillViewport(true); scrollView.addView(mInner, layoutParamsInner); LayoutParams layoutParams = new LayoutParams( @@ -139,9 +136,7 @@ public class RemoteComposePlayer extends FrameLayout { private void init(Context context, AttributeSet attrs, int defStyleAttr) { LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); - setBackgroundColor(Color.TRANSPARENT); mInner = new RemoteComposeCanvas(context, attrs, defStyleAttr); - mInner.setBackgroundColor(Color.TRANSPARENT); addView(mInner, layoutParams); } 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 d0d6e6982a16..ecb68bb21fb3 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 @@ -26,6 +26,7 @@ import android.graphics.PorterDuffColorFilter; import android.graphics.RadialGradient; import android.graphics.Rect; import android.graphics.RectF; +import android.graphics.RuntimeShader; import android.graphics.Shader; import android.graphics.SweepGradient; import android.graphics.Typeface; @@ -33,6 +34,8 @@ import android.graphics.Typeface; import com.android.internal.widget.remotecompose.core.PaintContext; import com.android.internal.widget.remotecompose.core.RemoteContext; import com.android.internal.widget.remotecompose.core.operations.ClipPath; +import com.android.internal.widget.remotecompose.core.operations.ShaderData; +import com.android.internal.widget.remotecompose.core.operations.Utils; import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle; import com.android.internal.widget.remotecompose.core.operations.paint.PaintChanges; @@ -43,6 +46,7 @@ import com.android.internal.widget.remotecompose.core.operations.paint.PaintChan public class AndroidPaintContext extends PaintContext { Paint mPaint = new Paint(); Canvas mCanvas; + Rect mTmpRect = new Rect(); // use in calculation of bounds public AndroidPaintContext(RemoteContext context, Canvas canvas) { super(context); @@ -177,6 +181,22 @@ public class AndroidPaintContext extends PaintContext { } @Override + public void getTextBounds(int textId, int start, int end, boolean monospace, float[] bounds) { + String str = getText(textId); + if (end == -1) { + end = str.length(); + } + + mPaint.getTextBounds(str, start, end, mTmpRect); + + bounds[0] = mTmpRect.left; + bounds[1] = mTmpRect.top; + bounds[2] = monospace ? (mPaint.measureText(str, start, end) - mTmpRect.left) + : mTmpRect.right; + bounds[3] = mTmpRect.bottom; + } + + @Override public void drawTextRun(int textID, int start, int end, @@ -185,7 +205,16 @@ public class AndroidPaintContext extends PaintContext { float x, float y, boolean rtl) { - String textToPaint = getText(textID).substring(start, end); + + String textToPaint = getText(textID); + if (end == -1) { + if (start != 0) { + textToPaint = textToPaint.substring(start); + } + } else { + textToPaint = textToPaint.substring(start, end); + } + mCanvas.drawText(textToPaint, x, y, mPaint); } @@ -308,7 +337,7 @@ public class AndroidPaintContext extends PaintContext { @Override public void applyPaint(PaintBundle mPaintData) { - mPaintData.applyPaintChange(new PaintChanges() { + mPaintData.applyPaintChange((PaintContext) this, new PaintChanges() { @Override public void setTextSize(float size) { mPaint.setTextSize(size); @@ -361,10 +390,8 @@ public class AndroidPaintContext extends PaintContext { } } - } - @Override public void setStrokeWidth(float width) { mPaint.setStrokeWidth(width); @@ -386,13 +413,37 @@ public class AndroidPaintContext extends PaintContext { } @Override - public void setShader(int shader, String shaderString) { - + public void setShader(int shaderId) { + // TODO this stuff should check the shader creation + if (shaderId == 0) { + mPaint.setShader(null); + return; + } + ShaderData data = getShaderData(shaderId); + RuntimeShader shader = new RuntimeShader(getText(data.getShaderTextId())); + String[] names = data.getUniformFloatNames(); + for (int i = 0; i < names.length; i++) { + String name = names[i]; + float[] val = data.getUniformFloats(name); + shader.setFloatUniform(name, val); + } + names = data.getUniformIntegerNames(); + for (int i = 0; i < names.length; i++) { + String name = names[i]; + int[] val = data.getUniformInts(name); + shader.setIntUniform(name, val); + } + names = data.getUniformBitmapNames(); + for (int i = 0; i < names.length; i++) { + String name = names[i]; + int val = data.getUniformBitmapId(name); + } + mPaint.setShader(shader); } @Override public void setImageFilterQuality(int quality) { - System.out.println(">>>>>>>>>>>> "); + Utils.log(" quality =" + quality); } @Override @@ -420,7 +471,6 @@ public class AndroidPaintContext extends PaintContext { mPaint.setFilterBitmap(filter); } - @Override public void setAntiAlias(boolean aa) { mPaint.setAntiAlias(aa); @@ -437,7 +487,6 @@ public class AndroidPaintContext extends PaintContext { case PaintBundle.COLOR_FILTER: mPaint.setColorFilter(null); - System.out.println(">>>>>>>>>>>>> CLEAR!!!!"); break; } } @@ -446,12 +495,11 @@ public class AndroidPaintContext extends PaintContext { } } - Shader.TileMode[] mTilesModes = new Shader.TileMode[]{ + Shader.TileMode[] mTileModes = new Shader.TileMode[]{ Shader.TileMode.CLAMP, Shader.TileMode.REPEAT, Shader.TileMode.MIRROR}; - @Override public void setLinearGradient(int[] colors, float[] stops, @@ -463,7 +511,7 @@ public class AndroidPaintContext extends PaintContext { mPaint.setShader(new LinearGradient(startX, startY, endX, - endY, colors, stops, mTilesModes[tileMode])); + endY, colors, stops, mTileModes[tileMode])); } @@ -475,7 +523,7 @@ public class AndroidPaintContext extends PaintContext { float radius, int tileMode) { mPaint.setShader(new RadialGradient(centerX, centerY, radius, - colors, stops, mTilesModes[tileMode])); + colors, stops, mTileModes[tileMode])); } @Override @@ -490,7 +538,6 @@ public class AndroidPaintContext extends PaintContext { @Override public void setColorFilter(int color, int mode) { PorterDuff.Mode pmode = origamiToPorterDuffMode(mode); - System.out.println("setting color filter to " + pmode.name()); if (pmode != null) { mPaint.setColorFilter( new PorterDuffColorFilter(color, pmode)); @@ -500,10 +547,10 @@ public class AndroidPaintContext extends PaintContext { } @Override - public void mtrixScale(float scaleX, - float scaleY, - float centerX, - float centerY) { + public void matrixScale(float scaleX, + float scaleY, + float centerX, + float centerY) { if (Float.isNaN(centerX)) { mCanvas.scale(scaleX, scaleY); } else { @@ -556,6 +603,11 @@ public class AndroidPaintContext extends PaintContext { } } + @Override + public void reset() { + mPaint.reset(); + } + private Path getPath(int path1Id, int path2Id, float tween, @@ -599,5 +651,9 @@ public class AndroidPaintContext extends PaintContext { private String getText(int id) { return (String) mContext.mRemoteComposeState.getFromId(id); } + + private ShaderData getShaderData(int id) { + return (ShaderData) mContext.mRemoteComposeState.getFromId(id); + } } 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 270e96f11942..6e4893bc0ee6 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,10 +20,15 @@ import android.graphics.BitmapFactory; import android.graphics.Canvas; import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.VariableSupport; +import com.android.internal.widget.remotecompose.core.operations.FloatExpression; +import com.android.internal.widget.remotecompose.core.operations.ShaderData; + +import java.util.HashMap; /** * An implementation of Context for Android. - * + * <p> * This is used to play the RemoteCompose operations on Android. */ class AndroidRemoteContext extends RemoteContext { @@ -33,6 +38,7 @@ class AndroidRemoteContext extends RemoteContext { mPaintContext = new AndroidPaintContext(this, canvas); } else { // need to make sure to update the canvas for the current one + mPaintContext.reset(); ((AndroidPaintContext) mPaintContext).setCanvas(canvas); } mWidth = canvas.getWidth(); @@ -50,13 +56,32 @@ class AndroidRemoteContext extends RemoteContext { } } + static class VarName { + String mName; + int mId; + int mType; + + VarName(String name, int id, int type) { + mName = name; + mId = id; + mType = type; + } + } + + HashMap<String, VarName> mVarNameHashMap = new HashMap<>(); + + @Override + public void loadVariableName(String varName, int varId, int varType) { + mVarNameHashMap.put(varName, new VarName(varName, varId, varType)); + } + /** * Decode a byte array into an image and cache it using the given imageId * - * @oaram imageId the id of the image - * @param width with of image to be loaded + * @param width with of image to be loaded * @param height height of image to be loaded * @param bitmap a byte array containing the image information + * @oaram imageId the id of the image */ @Override public void loadBitmap(int imageId, int width, int height, byte[] bitmap) { @@ -70,14 +95,66 @@ class AndroidRemoteContext extends RemoteContext { public void loadText(int id, String text) { if (!mRemoteComposeState.containsId(id)) { mRemoteComposeState.cache(id, text); + } else { + mRemoteComposeState.update(id, text); } } + @Override + public String getText(int id) { + return (String) mRemoteComposeState.getFromId(id); + } + + @Override + public void loadFloat(int id, float value) { + mRemoteComposeState.updateFloat(id, value); + } + + + @Override + public void loadColor(int id, int color) { + mRemoteComposeState.updateColor(id, color); + } + + @Override + public void loadAnimatedFloat(int id, FloatExpression animatedFloat) { + mRemoteComposeState.cache(id, animatedFloat); + } + + @Override + public void loadShader(int id, ShaderData value) { + mRemoteComposeState.cache(id, value); + } + + @Override + public float getFloat(int id) { + return (float) mRemoteComposeState.getFloat(id); + } + + @Override + public int getColor(int id) { + return mRemoteComposeState.getColor(id); + } + + @Override + public void listensTo(int id, VariableSupport variableSupport) { + mRemoteComposeState.listenToVar(id, variableSupport); + } + + @Override + public int updateOps() { + return mRemoteComposeState.getOpsToUpdate(this); + } + + @Override + public ShaderData getShader(int id) { + return (ShaderData) mRemoteComposeState.getFromId(id); + } + /////////////////////////////////////////////////////////////////////////////////////////////// // Click handling /////////////////////////////////////////////////////////////////////////////////////////////// - @Override public void addClickArea(int id, int contentDescriptionId, @@ -87,7 +164,7 @@ class AndroidRemoteContext extends RemoteContext { float bottom, int metadataId) { String contentDescription = (String) mRemoteComposeState.getFromId(contentDescriptionId); - String metadata = (String) mRemoteComposeState.getFromId(metadataId); + String metadata = (String) mRemoteComposeState.getFromId(metadataId); mDocument.addClickArea(id, contentDescription, left, top, right, bottom, metadata); } } diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/ClickAreaView.java b/core/java/com/android/internal/widget/remotecompose/player/platform/ClickAreaView.java index 672dae32d8e0..329178abe8b5 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/platform/ClickAreaView.java +++ b/core/java/com/android/internal/widget/remotecompose/player/platform/ClickAreaView.java @@ -20,7 +20,6 @@ import android.graphics.Canvas; import android.graphics.Paint; import android.view.View; - /** * Implementation for the click handling */ @@ -40,7 +39,6 @@ class ClickAreaView extends View { setContentDescription(contentDescription); } - public void setDebug(boolean value) { if (mDebug != value) { mDebug = value; 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 a3bb73e22c59..97d23c84b5bd 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 @@ -85,6 +85,7 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta mDocument.initializeContext(mARContext); setContentDescription(mDocument.getDocument().getContentDescription()); requestLayout(); + invalidate(); } AndroidRemoteContext mARContext = new AndroidRemoteContext(); @@ -119,8 +120,7 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta removeAllViews(); } - - public interface ClickCallbacks { + public interface ClickCallbacks { void click(int id, String metadata); } @@ -213,6 +213,9 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta setMeasuredDimension(w, h); } + private int mCount; + private long mTime = System.nanoTime(); + @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); @@ -224,6 +227,17 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta mARContext.mWidth = getWidth(); mARContext.mHeight = getHeight(); mDocument.paint(mARContext, mTheme); + if (mDebug) { + mCount++; + if (System.nanoTime() - mTime > 1000000000L) { + System.out.println(" count " + mCount + " fps"); + mCount = 0; + mTime = System.nanoTime(); + } + } + if (mDocument.needsRepaint() > 0) { + invalidate(); + } } } diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index c6d4f3ad771d..4dfe000659ff 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4840,7 +4840,7 @@ See android.credentials.CredentialManager --> <string name="config_defaultCredentialManagerHybridService" translatable="false"></string> - + <!-- The component name, flattened to a string, for the system's credential manager autofill service. This service allows interceding autofill requests and routing them to credential manager. @@ -6503,6 +6503,9 @@ <string-array name="config_sharedLibrariesLoadedAfterApp" translatable="false"> </string-array> + <!-- the number of the max cached processes in the system. --> + <integer name="config_customizedMaxCachedProcesses">32</integer> + <!-- Whether this device should support taking app snapshots on closure --> <bool name="config_disableTaskSnapshots">false</bool> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 4442b5e4c044..cc74d023ee5e 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -5067,6 +5067,8 @@ code and resources provided by applications. --> <java-symbol type="array" name="config_sharedLibrariesLoadedAfterApp" /> + <java-symbol type="integer" name="config_customizedMaxCachedProcesses" /> + <java-symbol type="color" name="overview_background"/> <java-symbol type="bool" name="config_disableTaskSnapshots" /> diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java index d92d24d9e22d..94c281fa9fac 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java @@ -685,46 +685,53 @@ class DividerPresenter implements View.OnTouchListener { ? taskBounds.width() - mProperties.mDividerWidthPx : taskBounds.height() - mProperties.mDividerWidthPx; - if (isDraggingToFullscreenAllowed(mProperties.mDividerAttributes)) { - final float displayDensity = getDisplayDensity(); - return dividerPositionWithDraggingToFullscreenAllowed( - dividerPosition, - minPosition, - maxPosition, - fullyExpandedPosition, - velocity, - displayDensity); - } - return Math.clamp(dividerPosition, minPosition, maxPosition); + final float displayDensity = getDisplayDensity(); + final boolean isDraggingToFullscreenAllowed = + isDraggingToFullscreenAllowed(mProperties.mDividerAttributes); + return dividerPositionWithPositionOptions( + dividerPosition, + minPosition, + maxPosition, + fullyExpandedPosition, + velocity, + displayDensity, + isDraggingToFullscreenAllowed); } /** - * Returns the divider position given a set of position options. A snap algorithm is used to - * adjust the ending position to either fully expand one container or move the divider back to - * the specified min/max ratio depending on the dragging velocity. + * Returns the divider position given a set of position options. A snap algorithm can adjust + * the ending position to either fully expand one container or move the divider back to + * the specified min/max ratio depending on the dragging velocity and if dragging to fullscreen + * is allowed. */ @VisibleForTesting - static int dividerPositionWithDraggingToFullscreenAllowed(int dividerPosition, int minPosition, - int maxPosition, int fullyExpandedPosition, float velocity, float displayDensity) { - final float minDismissVelocityPxPerSecond = - MIN_DISMISS_VELOCITY_DP_PER_SECOND * displayDensity; + static int dividerPositionWithPositionOptions(int dividerPosition, int minPosition, + int maxPosition, int fullyExpandedPosition, float velocity, float displayDensity, + boolean isDraggingToFullscreenAllowed) { + if (isDraggingToFullscreenAllowed) { + final float minDismissVelocityPxPerSecond = + MIN_DISMISS_VELOCITY_DP_PER_SECOND * displayDensity; + if (dividerPosition < minPosition && velocity < -minDismissVelocityPxPerSecond) { + return 0; + } + if (dividerPosition > maxPosition && velocity > minDismissVelocityPxPerSecond) { + return fullyExpandedPosition; + } + } final float minFlingVelocityPxPerSecond = MIN_FLING_VELOCITY_DP_PER_SECOND * displayDensity; - if (dividerPosition < minPosition && velocity < -minDismissVelocityPxPerSecond) { - return 0; + if (Math.abs(velocity) >= minFlingVelocityPxPerSecond) { + return dividerPositionForFling( + dividerPosition, minPosition, maxPosition, velocity); } - if (dividerPosition > maxPosition && velocity > minDismissVelocityPxPerSecond) { - return fullyExpandedPosition; - } - if (Math.abs(velocity) < minFlingVelocityPxPerSecond) { - if (dividerPosition >= minPosition && dividerPosition <= maxPosition) { - return dividerPosition; - } - final int[] snapPositions = {0, minPosition, maxPosition, fullyExpandedPosition}; - return snap(dividerPosition, snapPositions); + if (dividerPosition >= minPosition && dividerPosition <= maxPosition) { + return dividerPosition; } - return dividerPositionForFling( - dividerPosition, minPosition, maxPosition, velocity); + return snap( + dividerPosition, + isDraggingToFullscreenAllowed + ? new int[] {0, minPosition, maxPosition, fullyExpandedPosition} + : new int[] {minPosition, maxPosition}); } /** diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java index af3c4dac5d67..4f51815ed05d 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java @@ -660,108 +660,241 @@ public class DividerPresenterTest { // Divider position is less than minPosition and the velocity is enough to be dismissed assertEquals( 0, // Closed position - DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed( + DividerPresenter.dividerPositionWithPositionOptions( 10 /* dividerPosition */, 30 /* minPosition */, 900 /* maxPosition */, 1200 /* fullyExpandedPosition */, -dismissVelocity, - displayDensity)); + displayDensity, + true /* isDraggingToFullscreenAllowed */)); // Divider position is greater than maxPosition and the velocity is enough to be dismissed assertEquals( 1200, // Fully expanded position - DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed( + DividerPresenter.dividerPositionWithPositionOptions( 1000 /* dividerPosition */, 30 /* minPosition */, 900 /* maxPosition */, 1200 /* fullyExpandedPosition */, dismissVelocity, - displayDensity)); + displayDensity, + true /* isDraggingToFullscreenAllowed */)); // Divider position is returned when the velocity is not fast enough for fling and is in // between minPosition and maxPosition assertEquals( 500, // dividerPosition is not snapped - DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed( + DividerPresenter.dividerPositionWithPositionOptions( 500 /* dividerPosition */, 30 /* minPosition */, 900 /* maxPosition */, 1200 /* fullyExpandedPosition */, nonFlingVelocity, - displayDensity)); + displayDensity, + true /* isDraggingToFullscreenAllowed */)); // Divider position is snapped when the velocity is not fast enough for fling and larger // than maxPosition assertEquals( 900, // Closest position is maxPosition - DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed( + DividerPresenter.dividerPositionWithPositionOptions( 950 /* dividerPosition */, 30 /* minPosition */, 900 /* maxPosition */, 1200 /* fullyExpandedPosition */, nonFlingVelocity, - displayDensity)); + displayDensity, + true /* isDraggingToFullscreenAllowed */)); // Divider position is snapped when the velocity is not fast enough for fling and smaller // than minPosition assertEquals( 30, // Closest position is minPosition - DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed( + DividerPresenter.dividerPositionWithPositionOptions( 20 /* dividerPosition */, 30 /* minPosition */, 900 /* maxPosition */, 1200 /* fullyExpandedPosition */, nonFlingVelocity, - displayDensity)); + displayDensity, + true /* isDraggingToFullscreenAllowed */)); // Divider position is in the closed to maxPosition bounds and the velocity is enough for // backward fling assertEquals( 2000, // maxPosition - DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed( + DividerPresenter.dividerPositionWithPositionOptions( 2200 /* dividerPosition */, 1000 /* minPosition */, 2000 /* maxPosition */, 2500 /* fullyExpandedPosition */, -flingVelocity, - displayDensity)); + displayDensity, + true /* isDraggingToFullscreenAllowed */)); // Divider position is not in the closed to maxPosition bounds and the velocity is enough // for backward fling assertEquals( 1000, // minPosition - DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed( + DividerPresenter.dividerPositionWithPositionOptions( 1200 /* dividerPosition */, 1000 /* minPosition */, 2000 /* maxPosition */, 2500 /* fullyExpandedPosition */, -flingVelocity, - displayDensity)); + displayDensity, + true /* isDraggingToFullscreenAllowed */)); // Divider position is in the closed to minPosition bounds and the velocity is enough for // forward fling assertEquals( 1000, // minPosition - DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed( + DividerPresenter.dividerPositionWithPositionOptions( 500 /* dividerPosition */, 1000 /* minPosition */, 2000 /* maxPosition */, 2500 /* fullyExpandedPosition */, flingVelocity, - displayDensity)); + displayDensity, + true /* isDraggingToFullscreenAllowed */)); // Divider position is not in the closed to minPosition bounds and the velocity is enough // for forward fling assertEquals( 2000, // maxPosition - DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed( + DividerPresenter.dividerPositionWithPositionOptions( 1200 /* dividerPosition */, 1000 /* minPosition */, 2000 /* maxPosition */, 2500 /* fullyExpandedPosition */, flingVelocity, - displayDensity)); + displayDensity, + true /* isDraggingToFullscreenAllowed */)); + } + + @Test + public void testDividerPositionWithDraggingToFullscreenNotAllowed() { + final float displayDensity = 600F; + final float nonFlingVelocity = MIN_FLING_VELOCITY_DP_PER_SECOND * displayDensity - 10f; + final float flingVelocity = MIN_FLING_VELOCITY_DP_PER_SECOND * displayDensity + 10f; + + // Divider position is returned when the velocity is not fast enough for fling and is in + // between minPosition and maxPosition + assertEquals( + 500, // dividerPosition is not snapped + DividerPresenter.dividerPositionWithPositionOptions( + 500 /* dividerPosition */, + 30 /* minPosition */, + 900 /* maxPosition */, + 1200 /* fullyExpandedPosition */, + nonFlingVelocity, + displayDensity, + false /* isDraggingToFullscreenAllowed */)); + + // Divider position is snapped when the velocity is not fast enough for fling and larger + // than maxPosition + assertEquals( + 900, // Closest position is maxPosition + DividerPresenter.dividerPositionWithPositionOptions( + 950 /* dividerPosition */, + 30 /* minPosition */, + 900 /* maxPosition */, + 1200 /* fullyExpandedPosition */, + nonFlingVelocity, + displayDensity, + false /* isDraggingToFullscreenAllowed */)); + + // Divider position is snapped when the velocity is not fast enough for fling and smaller + // than minPosition + assertEquals( + 30, // Closest position is minPosition + DividerPresenter.dividerPositionWithPositionOptions( + 20 /* dividerPosition */, + 30 /* minPosition */, + 900 /* maxPosition */, + 1200 /* fullyExpandedPosition */, + nonFlingVelocity, + displayDensity, + false /* isDraggingToFullscreenAllowed */)); + + // Divider position is snapped when the velocity is not fast enough for fling and at the + // closed position + assertEquals( + 30, // Closest position is minPosition + DividerPresenter.dividerPositionWithPositionOptions( + 0 /* dividerPosition */, + 30 /* minPosition */, + 900 /* maxPosition */, + 1200 /* fullyExpandedPosition */, + nonFlingVelocity, + displayDensity, + false /* isDraggingToFullscreenAllowed */)); + + // Divider position is snapped when the velocity is not fast enough for fling and at the + // fully expanded position + assertEquals( + 900, // Closest position is maxPosition + DividerPresenter.dividerPositionWithPositionOptions( + 1200 /* dividerPosition */, + 30 /* minPosition */, + 900 /* maxPosition */, + 1200 /* fullyExpandedPosition */, + nonFlingVelocity, + displayDensity, + false /* isDraggingToFullscreenAllowed */)); + + // Divider position is in the closed to maxPosition bounds and the velocity is enough for + // backward fling + assertEquals( + 2000, // maxPosition + DividerPresenter.dividerPositionWithPositionOptions( + 2200 /* dividerPosition */, + 1000 /* minPosition */, + 2000 /* maxPosition */, + 2500 /* fullyExpandedPosition */, + -flingVelocity, + displayDensity, + false /* isDraggingToFullscreenAllowed */)); + + // Divider position is not in the closed to maxPosition bounds and the velocity is enough + // for backward fling + assertEquals( + 1000, // minPosition + DividerPresenter.dividerPositionWithPositionOptions( + 1200 /* dividerPosition */, + 1000 /* minPosition */, + 2000 /* maxPosition */, + 2500 /* fullyExpandedPosition */, + -flingVelocity, + displayDensity, + false /* isDraggingToFullscreenAllowed */)); + + // Divider position is in the closed to minPosition bounds and the velocity is enough for + // forward fling + assertEquals( + 1000, // minPosition + DividerPresenter.dividerPositionWithPositionOptions( + 500 /* dividerPosition */, + 1000 /* minPosition */, + 2000 /* maxPosition */, + 2500 /* fullyExpandedPosition */, + flingVelocity, + displayDensity, + false /* isDraggingToFullscreenAllowed */)); + + // Divider position is not in the closed to minPosition bounds and the velocity is enough + // for forward fling + assertEquals( + 2000, // maxPosition + DividerPresenter.dividerPositionWithPositionOptions( + 1200 /* dividerPosition */, + 1000 /* minPosition */, + 2000 /* maxPosition */, + 2500 /* fullyExpandedPosition */, + flingVelocity, + displayDensity, + false /* isDraggingToFullscreenAllowed */)); } private TaskFragmentContainer createMockTaskFragmentContainer( diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt index 0efdbdc9376c..327e2059557c 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt @@ -456,5 +456,7 @@ class BubbleStackViewTest { override fun isStackExpanded(): Boolean = false override fun isShowingAsBubbleBar(): Boolean = false + + override fun hideCurrentInputMethod() {} } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 4a1da4daf1ad..644907361cd7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -592,11 +592,12 @@ public class BubbleController implements ConfigurationChangeListener, * Hides the current input method, wherever it may be focused, via InputMethodManagerInternal. */ void hideCurrentInputMethod() { + mBubblePositioner.setImeVisible(false /* visible */, 0 /* height */); int displayId = mWindowManager.getDefaultDisplay().getDisplayId(); try { mBarService.hideCurrentInputMethodForBubbles(displayId); } catch (RemoteException e) { - e.printStackTrace(); + Log.e(TAG, "Failed to hide IME", e); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt index b0d3cc4a5d5c..3d9bf032c1b0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt @@ -29,6 +29,7 @@ interface BubbleExpandedViewManager { fun setAppBubbleTaskId(key: String, taskId: Int) fun isStackExpanded(): Boolean fun isShowingAsBubbleBar(): Boolean + fun hideCurrentInputMethod() companion object { /** @@ -73,6 +74,10 @@ interface BubbleExpandedViewManager { override fun isStackExpanded(): Boolean = controller.isStackExpanded override fun isShowingAsBubbleBar(): Boolean = controller.isShowingAsBubbleBar + + override fun hideCurrentInputMethod() { + controller.hideCurrentInputMethod() + } } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index fac9bf6e2a4b..ed904e2ff766 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -2324,7 +2324,6 @@ public class BubbleStackView extends FrameLayout * not. */ void hideCurrentInputMethod() { - mPositioner.setImeVisible(false, 0); mManager.hideCurrentInputMethod(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java index 271fb9abce6a..a7da07d013c1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java @@ -82,6 +82,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView private static final int INVALID_TASK_ID = -1; private BubbleExpandedViewManager mManager; + private BubblePositioner mPositioner; private boolean mIsOverflow; private BubbleTaskViewHelper mBubbleTaskViewHelper; private BubbleBarMenuViewController mMenuViewController; @@ -160,6 +161,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView boolean isOverflow, @Nullable BubbleTaskView bubbleTaskView) { mManager = expandedViewManager; + mPositioner = positioner; mIsOverflow = isOverflow; if (mIsOverflow) { @@ -290,15 +292,27 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView } /** - * Hides the current modal menu view or collapses the bubble stack. - * Called from {@link BubbleBarLayerView} + * Hides the current modal menu if it is visible + * @return {@code true} if menu was visible and is hidden */ - public void hideMenuOrCollapse() { + public boolean hideMenuIfVisible() { if (mMenuViewController.isMenuVisible()) { - mMenuViewController.hideMenu(/* animated = */ true); - } else { - mManager.collapseStack(); + mMenuViewController.hideMenu(true /* animated */); + return true; + } + return false; + } + + /** + * Hides the IME if it is visible + * @return {@code true} if IME was visible + */ + public boolean hideImeIfVisible() { + if (mPositioner.isImeVisible()) { + mManager.hideCurrentInputMethod(); + return true; } + return false; } /** Updates the bubble shown in the expanded view. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java index 123cc7e9d488..1d51d83da8e1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -132,7 +132,7 @@ public class BubbleBarLayerView extends FrameLayout } }); - setOnClickListener(view -> hideMenuOrCollapse()); + setOnClickListener(view -> hideMenuOrImeOrCollapse()); } @Override @@ -217,7 +217,7 @@ public class BubbleBarLayerView extends FrameLayout @Override public void onBackPressed() { - hideMenuOrCollapse(); + hideMenuOrImeOrCollapse(); } }); @@ -344,15 +344,23 @@ public class BubbleBarLayerView extends FrameLayout addView(mDismissView); } - /** Hides the current modal education/menu view, expanded view or collapses the bubble stack */ - private void hideMenuOrCollapse() { + /** Hides the current modal education/menu view, IME or collapses the expanded view */ + private void hideMenuOrImeOrCollapse() { if (mEducationViewController.isEducationVisible()) { mEducationViewController.hideEducation(/* animated = */ true); - } else if (isExpanded() && mExpandedView != null) { - mExpandedView.hideMenuOrCollapse(); - } else { - mBubbleController.collapseStack(); + return; + } + if (isExpanded() && mExpandedView != null) { + boolean menuHidden = mExpandedView.hideMenuIfVisible(); + if (menuHidden) { + return; + } + boolean imeHidden = mExpandedView.hideImeIfVisible(); + if (imeHidden) { + return; + } } + mBubbleController.collapseStack(); } /** Updates the expanded view size and position. */ diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl index efbf8da6d17c..eeb4853afadc 100644 --- a/media/java/android/media/IMediaRouterService.aidl +++ b/media/java/android/media/IMediaRouterService.aidl @@ -52,6 +52,7 @@ interface IMediaRouterService { // Methods for MediaRouter2 List<MediaRoute2Info> getSystemRoutes(String callerPackageName, boolean isProxyRouter); RoutingSessionInfo getSystemSessionInfo(); + boolean showMediaOutputSwitcherWithRouter2(String packageName); void registerRouter2(IMediaRouter2 router, String packageName); void unregisterRouter2(IMediaRouter2 router); @@ -97,5 +98,5 @@ interface IMediaRouterService { void setSessionVolumeWithManager(IMediaRouter2Manager manager, int requestId, String sessionId, int volume); void releaseSessionWithManager(IMediaRouter2Manager manager, int requestId, String sessionId); - boolean showMediaOutputSwitcher(String packageName); + boolean showMediaOutputSwitcherWithProxyRouter(IMediaRouter2Manager manager); } diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 5672cd54e369..0667bfda7596 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -2666,8 +2666,11 @@ public final class MediaRouter2 { @Override public boolean showSystemOutputSwitcher() { - throw new UnsupportedOperationException( - "Cannot show system output switcher from a privileged router."); + try { + return mMediaRouterService.showMediaOutputSwitcherWithProxyRouter(mClient); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } } /** Gets the list of all discovered routes. */ @@ -3539,7 +3542,7 @@ public final class MediaRouter2 { public boolean showSystemOutputSwitcher() { synchronized (mLock) { try { - return mMediaRouterService.showMediaOutputSwitcher(mImpl.getPackageName()); + return mMediaRouterService.showMediaOutputSwitcherWithRouter2(mPackageName); } catch (RemoteException ex) { ex.rethrowFromSystemServer(); } diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml index 38ad560d1bf0..5a4d3ce5661b 100644 --- a/packages/SettingsLib/res/values/arrays.xml +++ b/packages/SettingsLib/res/values/arrays.xml @@ -494,6 +494,26 @@ <item>show_deuteranomaly</item> </string-array> + <!-- Titles for app process limit preference. [CHAR LIMIT=35] --> + <string-array name="app_process_limit_entries"> + <item>Standard limit</item> + <item>No background processes</item> + <item>At most 1 process</item> + <item>At most 2 processes</item> + <item>At most 3 processes</item> + <item>At most 4 processes</item> + </string-array> + + <!-- Values for app process limit preference. --> + <string-array name="app_process_limit_values" translatable="false" > + <item>-1</item> + <item>0</item> + <item>1</item> + <item>2</item> + <item>3</item> + <item>4</item> + </string-array> + <!-- USB configuration names for Developer Settings. This can be overridden by devices with additional USB configurations. --> <string-array name="usb_configuration_titles"> diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 927aa6930ec3..363045ec1d83 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -975,6 +975,9 @@ <string name="immediately_destroy_activities_summary">Destroy every activity as soon as the user leaves it</string> + <!-- UI debug setting: limit number of running background processes [CHAR LIMIT=25] --> + <string name="app_process_limit_title">Background process limit</string> + <!-- UI debug setting: show all ANRs? [CHAR LIMIT=25] --> <string name="show_all_anrs">Show background ANRs</string> <!-- UI debug setting: show all ANRs summary [CHAR LIMIT=100] --> diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt index c32938497147..a1f8f1b32f77 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -1,6 +1,6 @@ package com.android.systemui.communal.ui.compose -import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.CubicBezierEasing import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.infiniteRepeatable @@ -20,20 +20,18 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.composed import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.dimensionResource -import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.CommunalSwipeDetector @@ -217,6 +215,7 @@ private fun SceneScope.CommunalScene( CommunalBackgroundType.DEFAULT -> DefaultBackground(colors = colors) CommunalBackgroundType.STATIC_GRADIENT -> StaticLinearGradient() CommunalBackgroundType.ANIMATED -> AnimatedLinearGradient() + CommunalBackgroundType.NONE -> BackgroundTopScrim() } } with(content) { Content(modifier = modifier) } @@ -252,7 +251,8 @@ private fun BoxScope.AnimatedLinearGradient() { val colors = LocalAndroidColorScheme.current Box( Modifier.matchParentSize() - .animatedGradientBackground(colors = listOf(colors.primary, colors.primaryContainer)) + .background(colors.primary) + .animatedRadialGradientBackground(colors.primary, colors.primaryContainer) ) BackgroundTopScrim() } @@ -265,29 +265,76 @@ private fun BoxScope.BackgroundTopScrim() { Box(Modifier.matchParentSize().alpha(0.34f).background(scrimOnTopColor)) } -/** Modifier which sets the background of a composable to an animated gradient */ +/** The duration to use for the gradient background animation. */ +private const val ANIMATION_DURATION_MS = 10_000 + +/** The offset to use in order to place the center of each gradient offscreen. */ +private val ANIMATION_OFFSCREEN_OFFSET = 128.dp + +/** Modifier which creates two radial gradients that animate up and down. */ @Composable -private fun Modifier.animatedGradientBackground(colors: List<Color>): Modifier = composed { - var size by remember { mutableStateOf(IntSize.Zero) } - val transition = rememberInfiniteTransition(label = "scrim background") - val startOffsetX by - transition.animateFloat( - initialValue = -size.width.toFloat(), - targetValue = size.width.toFloat(), +fun Modifier.animatedRadialGradientBackground(toColor: Color, fromColor: Color): Modifier { + val density = LocalDensity.current + val infiniteTransition = rememberInfiniteTransition(label = "radial gradient transition") + val centerFraction by + infiniteTransition.animateFloat( + initialValue = 0f, + targetValue = 1f, animationSpec = infiniteRepeatable( - animation = tween(durationMillis = 5_000, easing = LinearEasing), - repeatMode = RepeatMode.Reverse, + animation = + tween( + durationMillis = ANIMATION_DURATION_MS, + easing = CubicBezierEasing(0.33f, 0f, 0.67f, 1f), + ), + repeatMode = RepeatMode.Reverse ), - label = "scrim start offset" + label = "radial gradient center fraction" ) - background( + + // Offset to place the center of the gradients offscreen. This is applied to both the + // x and y coordinates. + val offsetPx = remember(density) { with(density) { ANIMATION_OFFSCREEN_OFFSET.toPx() } } + + return drawBehind { + val gradientRadius = (size.width / 2) + offsetPx + val totalHeight = size.height + 2 * offsetPx + + val leftCenter = + Offset( + x = -offsetPx, + y = totalHeight * centerFraction - offsetPx, + ) + val rightCenter = + Offset( + x = offsetPx + size.width, + y = totalHeight * (1f - centerFraction) - offsetPx, + ) + + // Right gradient + drawCircle( brush = - Brush.linearGradient( - colors = colors, - start = Offset(startOffsetX, 0f), - end = Offset(startOffsetX + size.width.toFloat(), size.height.toFloat()), - ) + Brush.radialGradient( + colors = listOf(fromColor, toColor), + center = rightCenter, + radius = gradientRadius + ), + center = rightCenter, + radius = gradientRadius, + blendMode = BlendMode.SrcAtop, ) - .onGloballyPositioned { size = it.size } + + // Left gradient + drawCircle( + brush = + Brush.radialGradient( + colors = listOf(fromColor, toColor), + center = leftCenter, + radius = gradientRadius + ), + center = leftCenter, + radius = gradientRadius, + blendMode = BlendMode.SrcAtop, + ) + } } diff --git a/packages/SystemUI/res/layout/auth_container_view.xml b/packages/SystemUI/res/layout/auth_container_view.xml index 2a1ce1fc5ec6..cc5a27d60ec1 100644 --- a/packages/SystemUI/res/layout/auth_container_view.xml +++ b/packages/SystemUI/res/layout/auth_container_view.xml @@ -28,9 +28,9 @@ <View android:id="@+id/panel" + style="@style/AuthNonCredentialPanelStyle" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="?androidprv:attr/materialColorSurfaceContainer" android:elevation="@dimen/biometric_dialog_elevation"/> <ScrollView diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 1e0adec4e84f..73b7586f1210 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -338,8 +338,11 @@ <item name="android:textSize">16sp</item> </style> - <style name="AuthCredentialPanelStyle"> + <style name="AuthNonCredentialPanelStyle"> <item name="android:background">?androidprv:attr/materialColorSurfaceBright</item> + </style> + + <style name="AuthCredentialPanelStyle" parent="AuthNonCredentialPanelStyle"> <item name="android:clickable">true</item> <item name="android:clipToOutline">true</item> <item name="android:importantForAccessibility">no</item> diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 177aad9a40fc..430ff0716ee0 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -515,7 +515,9 @@ public class AuthContainerView extends LinearLayout } else { throw new IllegalStateException("Unknown credential type: " + credentialType); } - mCredentialView = factory.inflate(layoutResourceId, null, false); + // TODO(b/288175645): Once AuthContainerView is removed, set 0dp in credential view xml + // files with the corresponding left/right or top/bottom constraints being set to "parent". + mCredentialView = factory.inflate(layoutResourceId, mLayout, false); // The background is used for detecting taps / cancelling authentication. Since the // credential view is full-screen and should not be canceled from background taps, diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalBackgroundType.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalBackgroundType.kt index 8b816db53970..4eaba065e078 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalBackgroundType.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalBackgroundType.kt @@ -21,4 +21,5 @@ enum class CommunalBackgroundType(val value: Int) { DEFAULT(0), STATIC_GRADIENT(1), ANIMATED(2), + NONE(3), } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt index d09b9f68ea60..5d541260b05f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt @@ -38,6 +38,7 @@ constructor( ) : KeyguardQuickAffordanceConfig { override val key: String = BuiltInKeyguardQuickAffordanceKeys.GLANCEABLE_HUB + override fun pickerName(): String = "Glanceable hub" override val pickerIconResourceId = R.drawable.ic_widgets @@ -52,6 +53,10 @@ constructor( } } + override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState { + return KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice + } + override fun onTriggered( expandable: Expandable? ): KeyguardQuickAffordanceConfig.OnTriggeredResult { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt index 8e985e11732f..37dffd1955d6 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt @@ -107,6 +107,7 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch @@ -371,6 +372,7 @@ class MediaDataProcessor( .onStart { emit(Unit) } .map { allowMediaRecommendations() } .distinctUntilChanged() + .flowOn(backgroundDispatcher) // only track the most recent emission .collectLatest { allowMediaRecommendations = it diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java index 63a183d506f8..959300629cd7 100644 --- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java @@ -272,8 +272,10 @@ class UiAutomationManager { mMainHandler.post(() -> { try { final IAccessibilityServiceClient serviceInterface; + final UiAutomationService uiAutomationService; synchronized (mLock) { serviceInterface = mServiceInterface; + uiAutomationService = mUiAutomationService; if (serviceInterface == null) { mService = null; } else { @@ -283,8 +285,8 @@ class UiAutomationManager { } // If the serviceInterface is null, the UiAutomation has been shut down on // another thread. - if (serviceInterface != null) { - mUiAutomationService.addWindowTokensForAllDisplays(); + if (serviceInterface != null && uiAutomationService != null) { + uiAutomationService.addWindowTokensForAllDisplays(); if (mTrace.isA11yTracingEnabledForTypes( AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) { mTrace.logTrace("UiAutomationService.connectServiceUnknownThread", diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 95c0e0ee5272..26aa0535d43e 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -169,6 +169,7 @@ final class ActivityManagerConstants extends ContentObserver { */ static final String KEY_ENABLE_NEW_OOMADJ = "enable_new_oom_adj"; + private static final int DEFAULT_MAX_CACHED_PROCESSES = 1024; private static final boolean DEFAULT_PRIORITIZE_ALARM_BROADCASTS = true; private static final long DEFAULT_FGSERVICE_MIN_SHOWN_TIME = 2*1000; private static final long DEFAULT_FGSERVICE_MIN_REPORT_TIME = 3*1000; @@ -293,7 +294,12 @@ final class ActivityManagerConstants extends ContentObserver { private static final long DEFAULT_SERVICE_BACKGROUND_TIMEOUT = DEFAULT_SERVICE_TIMEOUT * 10; /** - * Maximum number of phantom processes. + * Maximum number of cached processes. + */ + private static final String KEY_MAX_CACHED_PROCESSES = "max_cached_processes"; + + /** + * Maximum number of cached processes. */ private static final String KEY_MAX_PHANTOM_PROCESSES = "max_phantom_processes"; @@ -440,6 +446,9 @@ final class ActivityManagerConstants extends ContentObserver { volatile int mProcStateDebugSetProcStateDelay = 0; volatile int mProcStateDebugSetUidStateDelay = 0; + // Maximum number of cached processes we will allow. + public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES; + // This is the amount of time we allow an app to settle after it goes into the background, // before we start restricting what it can do. public long BACKGROUND_SETTLE_TIME = DEFAULT_BACKGROUND_SETTLE_TIME; @@ -848,6 +857,24 @@ final class ActivityManagerConstants extends ContentObserver { private ContentResolver mResolver; private final KeyValueListParser mParser = new KeyValueListParser(','); + private int mOverrideMaxCachedProcesses = -1; + private final int mCustomizedMaxCachedProcesses; + + // The maximum number of cached processes we will keep around before killing them. + // NOTE: this constant is *only* a control to not let us go too crazy with + // keeping around processes on devices with large amounts of RAM. For devices that + // are tighter on RAM, the out of memory killer is responsible for killing background + // processes as RAM is needed, and we should *never* be relying on this limit to + // kill them. Also note that this limit only applies to cached background processes; + // we have no limit on the number of service, visible, foreground, or other such + // processes and the number of those processes does not count against the cached + // process limit. This will be initialized in the constructor. + public int CUR_MAX_CACHED_PROCESSES; + + // The maximum number of empty app processes we will let sit around. This will be + // initialized in the constructor. + public int CUR_MAX_EMPTY_PROCESSES; + /** @see #mNoKillCachedProcessesUntilBootCompleted */ private static final String KEY_NO_KILL_CACHED_PROCESSES_UNTIL_BOOT_COMPLETED = "no_kill_cached_processes_until_boot_completed"; @@ -879,6 +906,15 @@ final class ActivityManagerConstants extends ContentObserver { volatile long mNoKillCachedProcessesPostBootCompletedDurationMillis = DEFAULT_NO_KILL_CACHED_PROCESSES_POST_BOOT_COMPLETED_DURATION_MILLIS; + // The number of empty apps at which we don't consider it necessary to do + // memory trimming. + public int CUR_TRIM_EMPTY_PROCESSES = computeEmptyProcessLimit(MAX_CACHED_PROCESSES) / 2; + + // The number of cached at which we don't consider it necessary to do + // memory trimming. + public int CUR_TRIM_CACHED_PROCESSES = + (MAX_CACHED_PROCESSES - computeEmptyProcessLimit(MAX_CACHED_PROCESSES)) / 3; + /** @see #mNoKillCachedProcessesUntilBootCompleted */ private static final String KEY_MAX_EMPTY_TIME_MILLIS = "max_empty_time_millis"; @@ -1129,6 +1165,9 @@ final class ActivityManagerConstants extends ContentObserver { return; } switch (name) { + case KEY_MAX_CACHED_PROCESSES: + updateMaxCachedProcesses(); + break; case KEY_DEFAULT_BACKGROUND_ACTIVITY_STARTS_ENABLED: updateBackgroundActivityStarts(); break; @@ -1378,7 +1417,16 @@ final class ActivityManagerConstants extends ContentObserver { context.getResources().getStringArray( com.android.internal.R.array.config_keep_warming_services)) .map(ComponentName::unflattenFromString).collect(Collectors.toSet())); - + mCustomizedMaxCachedProcesses = context.getResources().getInteger( + com.android.internal.R.integer.config_customizedMaxCachedProcesses); + CUR_MAX_CACHED_PROCESSES = mCustomizedMaxCachedProcesses; + CUR_MAX_EMPTY_PROCESSES = computeEmptyProcessLimit(CUR_MAX_CACHED_PROCESSES); + + final int rawMaxEmptyProcesses = computeEmptyProcessLimit( + Integer.min(CUR_MAX_CACHED_PROCESSES, MAX_CACHED_PROCESSES)); + CUR_TRIM_EMPTY_PROCESSES = rawMaxEmptyProcesses / 2; + CUR_TRIM_CACHED_PROCESSES = (Integer.min(CUR_MAX_CACHED_PROCESSES, MAX_CACHED_PROCESSES) + - rawMaxEmptyProcesses) / 3; loadNativeBootDeviceConfigConstants(); mDefaultDisableAppProfilerPssProfiling = context.getResources().getBoolean( R.bool.config_am_disablePssProfiling); @@ -1433,6 +1481,19 @@ final class ActivityManagerConstants extends ContentObserver { DEFAULT_ENABLE_NEW_OOM_ADJ); } + public void setOverrideMaxCachedProcesses(int value) { + mOverrideMaxCachedProcesses = value; + updateMaxCachedProcesses(); + } + + public int getOverrideMaxCachedProcesses() { + return mOverrideMaxCachedProcesses; + } + + public static int computeEmptyProcessLimit(int totalProcessLimit) { + return totalProcessLimit/2; + } + @Override public void onChange(boolean selfChange, Uri uri) { if (uri == null) return; @@ -1933,6 +1994,29 @@ final class ActivityManagerConstants extends ContentObserver { mSystemServerAutomaticHeapDumpPackageName); } + private void updateMaxCachedProcesses() { + String maxCachedProcessesFlag = DeviceConfig.getProperty( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_MAX_CACHED_PROCESSES); + try { + CUR_MAX_CACHED_PROCESSES = mOverrideMaxCachedProcesses < 0 + ? (TextUtils.isEmpty(maxCachedProcessesFlag) + ? mCustomizedMaxCachedProcesses : Integer.parseInt(maxCachedProcessesFlag)) + : mOverrideMaxCachedProcesses; + } catch (NumberFormatException e) { + // Bad flag value from Phenotype, revert to default. + Slog.e(TAG, + "Unable to parse flag for max_cached_processes: " + maxCachedProcessesFlag, e); + CUR_MAX_CACHED_PROCESSES = mCustomizedMaxCachedProcesses; + } + CUR_MAX_EMPTY_PROCESSES = computeEmptyProcessLimit(CUR_MAX_CACHED_PROCESSES); + + final int rawMaxEmptyProcesses = computeEmptyProcessLimit( + Integer.min(CUR_MAX_CACHED_PROCESSES, MAX_CACHED_PROCESSES)); + CUR_TRIM_EMPTY_PROCESSES = rawMaxEmptyProcesses / 2; + CUR_TRIM_CACHED_PROCESSES = (Integer.min(CUR_MAX_CACHED_PROCESSES, MAX_CACHED_PROCESSES) + - rawMaxEmptyProcesses) / 3; + } + private void updateProactiveKillsEnabled() { PROACTIVE_KILLS_ENABLED = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, @@ -2191,6 +2275,8 @@ final class ActivityManagerConstants extends ContentObserver { pw.println("ACTIVITY MANAGER SETTINGS (dumpsys activity settings) " + Settings.Global.ACTIVITY_MANAGER_CONSTANTS + ":"); + pw.print(" "); pw.print(KEY_MAX_CACHED_PROCESSES); pw.print("="); + pw.println(MAX_CACHED_PROCESSES); pw.print(" "); pw.print(KEY_BACKGROUND_SETTLE_TIME); pw.print("="); pw.println(BACKGROUND_SETTLE_TIME); pw.print(" "); pw.print(KEY_FGSERVICE_MIN_SHOWN_TIME); pw.print("="); @@ -2391,6 +2477,14 @@ final class ActivityManagerConstants extends ContentObserver { pw.print("="); pw.println(MAX_PREVIOUS_TIME); pw.println(); + if (mOverrideMaxCachedProcesses >= 0) { + pw.print(" mOverrideMaxCachedProcesses="); pw.println(mOverrideMaxCachedProcesses); + } + pw.print(" mCustomizedMaxCachedProcesses="); pw.println(mCustomizedMaxCachedProcesses); + pw.print(" CUR_MAX_CACHED_PROCESSES="); pw.println(CUR_MAX_CACHED_PROCESSES); + pw.print(" CUR_MAX_EMPTY_PROCESSES="); pw.println(CUR_MAX_EMPTY_PROCESSES); + pw.print(" CUR_TRIM_EMPTY_PROCESSES="); pw.println(CUR_TRIM_EMPTY_PROCESSES); + pw.print(" CUR_TRIM_CACHED_PROCESSES="); pw.println(CUR_TRIM_CACHED_PROCESSES); pw.print(" OOMADJ_UPDATE_QUICK="); pw.println(OOMADJ_UPDATE_QUICK); pw.print(" ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION="); pw.println(mEnableWaitForFinishAttachApplication); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index e0ec17124318..2a724cdce456 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -5846,13 +5846,19 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void setProcessLimit(int max) { - // Process limits are deprecated since b/253908413 + enforceCallingPermission(android.Manifest.permission.SET_PROCESS_LIMIT, + "setProcessLimit()"); + synchronized (this) { + mConstants.setOverrideMaxCachedProcesses(max); + trimApplicationsLocked(true, OOM_ADJ_REASON_PROCESS_END); + } } @Override public int getProcessLimit() { - // Process limits are deprecated since b/253908413 - return Integer.MAX_VALUE; + synchronized (this) { + return mConstants.getOverrideMaxCachedProcesses(); + } } void importanceTokenDied(ImportanceToken token) { @@ -10201,6 +10207,19 @@ public class ActivityManagerService extends IActivityManager.Stub addStartInfoTimestampInternal(key, timestampNs, userId, callingUid); } + @Override + public void reportStartInfoViewTimestamps(long renderThreadDrawStartTimeNs, + long framePresentedTimeNs) { + int callingUid = Binder.getCallingUid(); + int userId = UserHandle.getUserId(callingUid); + addStartInfoTimestampInternal( + ApplicationStartInfo.START_TIMESTAMP_INITIAL_RENDERTHREAD_FRAME, + renderThreadDrawStartTimeNs, userId, callingUid); + addStartInfoTimestampInternal( + ApplicationStartInfo.START_TIMESTAMP_SURFACEFLINGER_COMPOSITION_COMPLETE, + framePresentedTimeNs, userId, callingUid); + } + private void addStartInfoTimestampInternal(int key, long timestampNs, int userId, int uid) { mProcessList.getAppStartInfoTracker().addTimestampToStart( Settings.getPackageNameForUid(mContext, uid), diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java index 3ed60fcae5e4..dda48adbf732 100644 --- a/services/core/java/com/android/server/am/AppProfiler.java +++ b/services/core/java/com/android/server/am/AppProfiler.java @@ -507,7 +507,8 @@ public class AppProfiler { final int lruSize = mService.mProcessList.getLruSizeLOSP(); if (mCachedAppFrozenDurations == null || mCachedAppFrozenDurations.length < lruSize) { - mCachedAppFrozenDurations = new long[lruSize]; + mCachedAppFrozenDurations = new long[Math.max( + lruSize, mService.mConstants.CUR_MAX_CACHED_PROCESSES)]; } mService.mProcessList.forEachLruProcessesLOSP(true, app -> { if (app.mOptRecord.isFrozen()) { @@ -1369,13 +1370,18 @@ public class AppProfiler { // are managing to keep around is less than half the maximum we desire; // if we are keeping a good number around, we'll let them use whatever // memory they want. - final int numCachedAndEmpty = numCached + numEmpty; - if (numCachedAndEmpty <= ProcessList.TRIM_CRITICAL_THRESHOLD) { - memFactor = ADJ_MEM_FACTOR_CRITICAL; - } else if (numCachedAndEmpty <= ProcessList.TRIM_LOW_THRESHOLD) { - memFactor = ADJ_MEM_FACTOR_LOW; + if (numCached <= mService.mConstants.CUR_TRIM_CACHED_PROCESSES + && numEmpty <= mService.mConstants.CUR_TRIM_EMPTY_PROCESSES) { + final int numCachedAndEmpty = numCached + numEmpty; + if (numCachedAndEmpty <= ProcessList.TRIM_CRITICAL_THRESHOLD) { + memFactor = ADJ_MEM_FACTOR_CRITICAL; + } else if (numCachedAndEmpty <= ProcessList.TRIM_LOW_THRESHOLD) { + memFactor = ADJ_MEM_FACTOR_LOW; + } else { + memFactor = ADJ_MEM_FACTOR_MODERATE; + } } else { - memFactor = ADJ_MEM_FACTOR_MODERATE; + memFactor = ADJ_MEM_FACTOR_NORMAL; } } // We always allow the memory level to go up (better). We only allow it to go diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java index a8227fa8e38b..dc6e2fa39b65 100644 --- a/services/core/java/com/android/server/am/AppStartInfoTracker.java +++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java @@ -1128,21 +1128,8 @@ public final class AppStartInfoTracker { // Records are sorted newest to oldest, grab record at index 0. ApplicationStartInfo startInfo = mInfos.get(0); - int startupState = startInfo.getStartupState(); - // If startup state is error then don't accept any further timestamps. - if (startupState == ApplicationStartInfo.STARTUP_STATE_ERROR) { - if (DEBUG) Slog.d(TAG, "Startup state is error, not accepting new timestamps."); - return; - } - - // If startup state is first frame drawn then only accept fully drawn timestamp. - if (startupState == ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN - && key != ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN) { - if (DEBUG) { - Slog.d(TAG, "Startup state is first frame drawn and timestamp is not fully " - + "drawn, not accepting new timestamps."); - } + if (!isAddTimestampAllowed(startInfo, key, timestampNs)) { return; } @@ -1155,6 +1142,55 @@ public final class AppStartInfoTracker { } } + private boolean isAddTimestampAllowed(ApplicationStartInfo startInfo, int key, + long timestampNs) { + int startupState = startInfo.getStartupState(); + + // If startup state is error then don't accept any further timestamps. + if (startupState == ApplicationStartInfo.STARTUP_STATE_ERROR) { + if (DEBUG) Slog.d(TAG, "Startup state is error, not accepting new timestamps."); + return false; + } + + Map<Integer, Long> timestamps = startInfo.getStartupTimestamps(); + + if (startupState == ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN) { + switch (key) { + case ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN: + // Allowed, continue to confirm it's not already added. + break; + case ApplicationStartInfo.START_TIMESTAMP_INITIAL_RENDERTHREAD_FRAME: + Long firstFrameTimeNs = timestamps + .get(ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME); + if (firstFrameTimeNs == null) { + // This should never happen. State can't be first frame drawn if first + // frame timestamp was not provided. + return false; + } + + if (timestampNs > firstFrameTimeNs) { + // Initial renderthread frame has to occur before first frame. + return false; + } + + // Allowed, continue to confirm it's not already added. + break; + case ApplicationStartInfo.START_TIMESTAMP_SURFACEFLINGER_COMPOSITION_COMPLETE: + // Allowed, continue to confirm it's not already added. + break; + default: + return false; + } + } + + if (timestamps.get(key) != null) { + // Timestamp should not occur more than once for a given start. + return false; + } + + return true; + } + @GuardedBy("mLock") void dumpLocked(PrintWriter pw, String prefix, SimpleDateFormat sdf) { if (mMonitoringModeEnabled) { diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index d642b02e23ea..29e0f7ae6f01 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -122,12 +122,14 @@ import com.android.server.net.BaseNetworkObserver; import com.android.server.pm.UserManagerInternal; import com.android.server.power.optimization.Flags; import com.android.server.power.stats.AggregatedPowerStatsConfig; +import com.android.server.power.stats.AudioPowerStatsProcessor; import com.android.server.power.stats.BatteryExternalStatsWorker; import com.android.server.power.stats.BatteryStatsDumpHelperImpl; import com.android.server.power.stats.BatteryStatsImpl; import com.android.server.power.stats.BatteryUsageStatsProvider; import com.android.server.power.stats.BluetoothPowerStatsProcessor; import com.android.server.power.stats.CpuPowerStatsProcessor; +import com.android.server.power.stats.FlashlightPowerStatsProcessor; import com.android.server.power.stats.MobileRadioPowerStatsProcessor; import com.android.server.power.stats.PhoneCallPowerStatsProcessor; import com.android.server.power.stats.PowerStatsAggregator; @@ -136,6 +138,7 @@ import com.android.server.power.stats.PowerStatsScheduler; import com.android.server.power.stats.PowerStatsStore; import com.android.server.power.stats.PowerStatsUidResolver; import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes; +import com.android.server.power.stats.VideoPowerStatsProcessor; import com.android.server.power.stats.WifiPowerStatsProcessor; import com.android.server.power.stats.wakeups.CpuWakeupStats; @@ -194,7 +197,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub private final BatteryUsageStatsProvider mBatteryUsageStatsProvider; private final AtomicFile mConfigFile; private final BatteryStats.BatteryStatsDumpHelper mDumpHelper; - private final PowerStatsUidResolver mPowerStatsUidResolver; + private final PowerStatsUidResolver mPowerStatsUidResolver = new PowerStatsUidResolver(); private final AggregatedPowerStatsConfig mAggregatedPowerStatsConfig; private volatile boolean mMonitorEnabled = true; @@ -422,7 +425,6 @@ public final class BatteryStatsService extends IBatteryStats.Stub setPowerStatsThrottlePeriods(batteryStatsConfigBuilder, context.getResources().getString( com.android.internal.R.string.config_powerStatsThrottlePeriods)); mBatteryStatsConfig = batteryStatsConfigBuilder.build(); - mPowerStatsUidResolver = new PowerStatsUidResolver(); mStats = new BatteryStatsImpl(mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock, systemDir, mHandler, this, this, mUserManagerUserInfoProvider, mPowerProfile, mCpuScalingPolicies, mPowerStatsUidResolver); @@ -516,6 +518,42 @@ public final class BatteryStatsService extends IBatteryStats.Stub AggregatedPowerStatsConfig.STATE_PROCESS_STATE) .setProcessor( new BluetoothPowerStatsProcessor(mPowerProfile)); + + config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_AUDIO) + .trackDeviceStates( + AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN) + .trackUidStates( + AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN, + AggregatedPowerStatsConfig.STATE_PROCESS_STATE) + .setProcessor( + new AudioPowerStatsProcessor(mPowerProfile, + mPowerStatsUidResolver)); + + config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_VIDEO) + .trackDeviceStates( + AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN) + .trackUidStates( + AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN, + AggregatedPowerStatsConfig.STATE_PROCESS_STATE) + .setProcessor( + new VideoPowerStatsProcessor(mPowerProfile, + mPowerStatsUidResolver)); + + config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT) + .trackDeviceStates( + AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN) + .trackUidStates( + AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN, + AggregatedPowerStatsConfig.STATE_PROCESS_STATE) + .setProcessor( + new FlashlightPowerStatsProcessor(mPowerProfile, + mPowerStatsUidResolver)); return config; } @@ -583,6 +621,24 @@ public final class BatteryStatsService extends IBatteryStats.Stub BatteryConsumer.POWER_COMPONENT_BLUETOOTH, Flags.streamlinedConnectivityBatteryStats()); + mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_AUDIO, + Flags.streamlinedMiscBatteryStats()); + mBatteryUsageStatsProvider.setPowerStatsExporterEnabled( + BatteryConsumer.POWER_COMPONENT_AUDIO, + Flags.streamlinedMiscBatteryStats()); + + mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_VIDEO, + Flags.streamlinedMiscBatteryStats()); + mBatteryUsageStatsProvider.setPowerStatsExporterEnabled( + BatteryConsumer.POWER_COMPONENT_VIDEO, + Flags.streamlinedMiscBatteryStats()); + + mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT, + Flags.streamlinedMiscBatteryStats()); + mBatteryUsageStatsProvider.setPowerStatsExporterEnabled( + BatteryConsumer.POWER_COMPONENT_FLASHLIGHT, + Flags.streamlinedMiscBatteryStats()); + mWorker.systemServicesReady(); mStats.systemServicesReady(mContext); mCpuWakeupStats.systemServicesReady(); diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 105e201add52..ab34dd4477fd 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -470,7 +470,7 @@ public class OomAdjuster { return true; }); mTmpUidRecords = new ActiveUids(service, false); - mTmpQueue = new ArrayDeque<ProcessRecord>(); + mTmpQueue = new ArrayDeque<ProcessRecord>(mConstants.CUR_MAX_CACHED_PROCESSES << 1); mNumSlots = ((CACHED_APP_MAX_ADJ - CACHED_APP_MIN_ADJ + 1) >> 1) / CACHED_APP_IMPORTANCE_LEVELS; } @@ -1079,11 +1079,23 @@ public class OomAdjuster { int curEmptyAdj = CACHED_APP_MIN_ADJ + CACHED_APP_IMPORTANCE_LEVELS; int nextEmptyAdj = curEmptyAdj + (CACHED_APP_IMPORTANCE_LEVELS * 2); + final int emptyProcessLimit = mConstants.CUR_MAX_EMPTY_PROCESSES; + final int cachedProcessLimit = mConstants.CUR_MAX_CACHED_PROCESSES + - emptyProcessLimit; // Let's determine how many processes we have running vs. // how many slots we have for background processes; we may want // to put multiple processes in a slot of there are enough of // them. int numEmptyProcs = numLru - mNumNonCachedProcs - mNumCachedHiddenProcs; + if (numEmptyProcs > cachedProcessLimit) { + // If there are more empty processes than our limit on cached + // processes, then use the cached process limit for the factor. + // This ensures that the really old empty processes get pushed + // down to the bottom, so if we are running low on memory we will + // have a better chance at keeping around more cached processes + // instead of a gazillion empty processes. + numEmptyProcs = cachedProcessLimit; + } int cachedFactor = (mNumCachedHiddenProcs > 0 ? (mNumCachedHiddenProcs + mNumSlots - 1) : 1) / mNumSlots; @@ -1205,6 +1217,17 @@ public class OomAdjuster { ArrayList<ProcessRecord> lruList = mProcessList.getLruProcessesLOSP(); final int numLru = lruList.size(); + final boolean doKillExcessiveProcesses = shouldKillExcessiveProcesses(now); + if (!doKillExcessiveProcesses) { + if (mNextNoKillDebugMessageTime < now) { + Slog.d(TAG, "Not killing cached processes"); // STOPSHIP Remove it b/222365734 + mNextNoKillDebugMessageTime = now + 5000; // Every 5 seconds + } + } + final int emptyProcessLimit = doKillExcessiveProcesses + ? mConstants.CUR_MAX_EMPTY_PROCESSES : Integer.MAX_VALUE; + final int cachedProcessLimit = doKillExcessiveProcesses + ? (mConstants.CUR_MAX_CACHED_PROCESSES - emptyProcessLimit) : Integer.MAX_VALUE; int lastCachedGroup = 0; int lastCachedGroupUid = 0; int numCached = 0; @@ -1256,14 +1279,36 @@ public class OomAdjuster { } else { lastCachedGroupUid = lastCachedGroup = 0; } - if (proactiveKillsEnabled) { + if ((numCached - numCachedExtraGroup) > cachedProcessLimit) { + app.killLocked("cached #" + numCached, + "too many cached", + ApplicationExitInfo.REASON_OTHER, + ApplicationExitInfo.SUBREASON_TOO_MANY_CACHED, + true); + } else if (proactiveKillsEnabled) { lruCachedApp = app; } break; case PROCESS_STATE_CACHED_EMPTY: - numEmpty++; - if (proactiveKillsEnabled) { - lruCachedApp = app; + if (numEmpty > mConstants.CUR_TRIM_EMPTY_PROCESSES + && app.getLastActivityTime() < oldTime) { + app.killLocked("empty for " + ((now + - app.getLastActivityTime()) / 1000) + "s", + "empty for too long", + ApplicationExitInfo.REASON_OTHER, + ApplicationExitInfo.SUBREASON_TRIM_EMPTY, + true); + } else { + numEmpty++; + if (numEmpty > emptyProcessLimit) { + app.killLocked("empty #" + numEmpty, + "too many empty", + ApplicationExitInfo.REASON_OTHER, + ApplicationExitInfo.SUBREASON_TOO_MANY_EMPTY, + true); + } else if (proactiveKillsEnabled) { + lruCachedApp = app; + } } break; default: @@ -1304,6 +1349,7 @@ public class OomAdjuster { } if (proactiveKillsEnabled // Proactive kills enabled? + && doKillExcessiveProcesses // Should kill excessive processes? && freeSwapPercent < lowSwapThresholdPercent // Swap below threshold? && lruCachedApp != null // If no cached app, let LMKD decide // If swap is non-decreasing, give reclaim a chance to catch up @@ -1498,6 +1544,25 @@ public class OomAdjuster { } } + /** + * Return true if we should kill excessive cached/empty processes. + */ + private boolean shouldKillExcessiveProcesses(long nowUptime) { + final long lastUserUnlockingUptime = mService.mUserController.getLastUserUnlockingUptime(); + + if (lastUserUnlockingUptime == 0) { + // No users have been unlocked. + return !mConstants.mNoKillCachedProcessesUntilBootCompleted; + } + final long noKillCachedProcessesPostBootCompletedDurationMillis = + mConstants.mNoKillCachedProcessesPostBootCompletedDurationMillis; + if ((lastUserUnlockingUptime + noKillCachedProcessesPostBootCompletedDurationMillis) + > nowUptime) { + return false; + } + return true; + } + protected final ComputeOomAdjWindowCallback mTmpComputeOomAdjWindowCallback = new ComputeOomAdjWindowCallback(); diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index c03497e629f0..ba7d3b8c76d2 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -70,6 +70,7 @@ import com.android.internal.util.function.pooled.PooledLambda; import com.android.media.flags.Flags; import com.android.server.LocalServices; import com.android.server.pm.UserManagerInternal; +import com.android.server.statusbar.StatusBarManagerInternal; import java.io.PrintWriter; import java.lang.ref.WeakReference; @@ -118,6 +119,7 @@ class MediaRouter2ServiceImpl { private final UserManagerInternal mUserManagerInternal; private final Object mLock = new Object(); private final AppOpsManager mAppOpsManager; + private final StatusBarManagerInternal mStatusBarManagerInternal; final AtomicInteger mNextRouterOrManagerId = new AtomicInteger(1); final ActivityManager mActivityManager; final PowerManager mPowerManager; @@ -188,6 +190,7 @@ class MediaRouter2ServiceImpl { mPowerManager = mContext.getSystemService(PowerManager.class); mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); mAppOpsManager = mContext.getSystemService(AppOpsManager.class); + mStatusBarManagerInternal = LocalServices.getService(StatusBarManagerInternal.class); IntentFilter screenOnOffIntentFilter = new IntentFilter(); screenOnOffIntentFilter.addAction(ACTION_SCREEN_ON); @@ -260,6 +263,17 @@ class MediaRouter2ServiceImpl { } } + @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS) + public boolean showMediaOutputSwitcherWithRouter2(@NonNull String packageName) { + UserHandle userHandle = Binder.getCallingUserHandle(); + final long token = Binder.clearCallingIdentity(); + try { + return showOutputSwitcher(packageName, userHandle); + } finally { + Binder.restoreCallingIdentity(token); + } + } + public void registerRouter2(@NonNull IMediaRouter2 router, @NonNull String packageName) { Objects.requireNonNull(router, "router must not be null"); if (TextUtils.isEmpty(packageName)) { @@ -778,6 +792,31 @@ class MediaRouter2ServiceImpl { } } + @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS) + public boolean showMediaOutputSwitcherWithProxyRouter( + @NonNull IMediaRouter2Manager proxyRouter) { + Objects.requireNonNull(proxyRouter, "Proxy router must not be null"); + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + final IBinder binder = proxyRouter.asBinder(); + ManagerRecord proxyRouterRecord = mAllManagerRecords.get(binder); + + if (proxyRouterRecord.mTargetPackageName == null) { + throw new UnsupportedOperationException( + "Only proxy routers can show the Output Switcher."); + } + + return showOutputSwitcher( + proxyRouterRecord.mTargetPackageName, + UserHandle.of(proxyRouterRecord.mUserRecord.mUserId)); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + // End of methods that implement MediaRouter2Manager operations. // Start of methods that implements operations for both MediaRouter2 and MediaRouter2Manager. @@ -934,6 +973,19 @@ class MediaRouter2ServiceImpl { } } + @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS) + private boolean showOutputSwitcher( + @NonNull String packageName, @NonNull UserHandle userHandle) { + if (mActivityManager.getPackageImportance(packageName) > IMPORTANCE_FOREGROUND) { + Slog.w(TAG, "showMediaOutputSwitcher only works when called from foreground"); + return false; + } + synchronized (mLock) { + mStatusBarManagerInternal.showMediaOutputSwitcher(packageName, userHandle); + } + return true; + } + // End of methods that implements operations for both MediaRouter2 and MediaRouter2Manager. public void dump(@NonNull PrintWriter pw, @NonNull String prefix) { diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index 192ac6287884..1188a0764051 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -16,7 +16,6 @@ package com.android.server.media; -import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import android.Manifest; import android.annotation.NonNull; @@ -75,7 +74,6 @@ import com.android.media.flags.Flags; import com.android.server.LocalServices; import com.android.server.Watchdog; import com.android.server.pm.UserManagerInternal; -import com.android.server.statusbar.StatusBarManagerInternal; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -266,32 +264,6 @@ public final class MediaRouterService extends IMediaRouterService.Stub // Binder call @Override - public boolean showMediaOutputSwitcher(String packageName) { - int uid = Binder.getCallingUid(); - if (!validatePackageName(uid, packageName)) { - throw new SecurityException("packageName must match the calling identity"); - } - UserHandle userHandle = UserHandle.getUserHandleForUid(uid); - final long token = Binder.clearCallingIdentity(); - try { - if (mContext.getSystemService(ActivityManager.class).getPackageImportance(packageName) - > IMPORTANCE_FOREGROUND) { - Slog.w(TAG, "showMediaOutputSwitcher only works when called from foreground"); - return false; - } - synchronized (mLock) { - StatusBarManagerInternal statusBar = - LocalServices.getService(StatusBarManagerInternal.class); - statusBar.showMediaOutputSwitcher(packageName, userHandle); - } - } finally { - Binder.restoreCallingIdentity(token); - } - return true; - } - - // Binder call - @Override public MediaRouterClientState getState(IMediaRouterClient client) { final long token = Binder.clearCallingIdentity(); try { @@ -443,6 +415,17 @@ public final class MediaRouterService extends IMediaRouterService.Stub } // Binder call + @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS) + @Override + public boolean showMediaOutputSwitcherWithRouter2(@NonNull String packageName) { + int uid = Binder.getCallingUid(); + if (!validatePackageName(uid, packageName)) { + throw new SecurityException("packageName must match the calling identity"); + } + return mService2.showMediaOutputSwitcherWithRouter2(packageName); + } + + // Binder call @Override public void registerRouter2(IMediaRouter2 router, String packageName) { final int uid = Binder.getCallingUid(); @@ -676,6 +659,13 @@ public final class MediaRouterService extends IMediaRouterService.Stub mService2.releaseSessionWithManager(manager, requestId, sessionId); } + @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS) + @Override + public boolean showMediaOutputSwitcherWithProxyRouter( + @NonNull IMediaRouter2Manager proxyRouter) { + return mService2.showMediaOutputSwitcherWithProxyRouter(proxyRouter); + } + void restoreBluetoothA2dp() { try { boolean a2dpOn; diff --git a/services/core/java/com/android/server/power/stats/AudioPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/AudioPowerStatsProcessor.java new file mode 100644 index 000000000000..a48f162321dd --- /dev/null +++ b/services/core/java/com/android/server/power/stats/AudioPowerStatsProcessor.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power.stats; + +import android.os.BatteryConsumer; +import android.os.BatteryStats; + +import com.android.internal.os.PowerProfile; + +public class AudioPowerStatsProcessor extends BinaryStatePowerStatsProcessor { + public AudioPowerStatsProcessor(PowerProfile powerProfile, + PowerStatsUidResolver uidResolver) { + super(BatteryConsumer.POWER_COMPONENT_AUDIO, uidResolver, + powerProfile.getAveragePower(PowerProfile.POWER_AUDIO)); + } + + @Override + protected @BinaryState int getBinaryState(BatteryStats.HistoryItem item) { + return (item.states & BatteryStats.HistoryItem.STATE_AUDIO_ON_FLAG) != 0 + ? STATE_ON + : STATE_OFF; + } +} diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index efaa7a8598c0..5bae5a42d484 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -6490,12 +6490,14 @@ public class BatteryStatsImpl extends BatteryStats { uid = mapUid(uid); if (mAudioOnNesting == 0) { mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE_AUDIO_ON_FLAG); + HistoryItem.STATE_AUDIO_ON_FLAG, uid, "audio"); mAudioOnTimer.startRunningLocked(elapsedRealtimeMs); } mAudioOnNesting++; - getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs) - .noteAudioTurnedOnLocked(elapsedRealtimeMs); + if (!mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_AUDIO)) { + getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs) + .noteAudioTurnedOnLocked(elapsedRealtimeMs); + } } @GuardedBy("this") @@ -6506,11 +6508,13 @@ public class BatteryStatsImpl extends BatteryStats { uid = mapUid(uid); if (--mAudioOnNesting == 0) { mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE_AUDIO_ON_FLAG); + HistoryItem.STATE_AUDIO_ON_FLAG, uid, "audio"); mAudioOnTimer.stopRunningLocked(elapsedRealtimeMs); } - getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs) - .noteAudioTurnedOffLocked(elapsedRealtimeMs); + if (!mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_AUDIO)) { + getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs) + .noteAudioTurnedOffLocked(elapsedRealtimeMs); + } } @GuardedBy("this") @@ -6518,12 +6522,14 @@ public class BatteryStatsImpl extends BatteryStats { uid = mapUid(uid); if (mVideoOnNesting == 0) { mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE2_VIDEO_ON_FLAG); + HistoryItem.STATE2_VIDEO_ON_FLAG, uid, "video"); mVideoOnTimer.startRunningLocked(elapsedRealtimeMs); } mVideoOnNesting++; - getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs) - .noteVideoTurnedOnLocked(elapsedRealtimeMs); + if (!mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_VIDEO)) { + getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs) + .noteVideoTurnedOnLocked(elapsedRealtimeMs); + } } @GuardedBy("this") @@ -6534,11 +6540,13 @@ public class BatteryStatsImpl extends BatteryStats { uid = mapUid(uid); if (--mVideoOnNesting == 0) { mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE2_VIDEO_ON_FLAG); + HistoryItem.STATE2_VIDEO_ON_FLAG, uid, "video"); mVideoOnTimer.stopRunningLocked(elapsedRealtimeMs); } - getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs) - .noteVideoTurnedOffLocked(elapsedRealtimeMs); + if (!mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_VIDEO)) { + getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs) + .noteVideoTurnedOffLocked(elapsedRealtimeMs); + } } @GuardedBy("this") @@ -6613,11 +6621,13 @@ public class BatteryStatsImpl extends BatteryStats { uid = mapUid(uid); if (mFlashlightOnNesting++ == 0) { mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE2_FLASHLIGHT_FLAG); + HistoryItem.STATE2_FLASHLIGHT_FLAG, uid, "flashlight"); mFlashlightOnTimer.startRunningLocked(elapsedRealtimeMs); } - getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs) - .noteFlashlightTurnedOnLocked(elapsedRealtimeMs); + if (!mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) { + getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs) + .noteFlashlightTurnedOnLocked(elapsedRealtimeMs); + } } @GuardedBy("this") @@ -6628,11 +6638,13 @@ public class BatteryStatsImpl extends BatteryStats { uid = mapUid(uid); if (--mFlashlightOnNesting == 0) { mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs, - HistoryItem.STATE2_FLASHLIGHT_FLAG); + HistoryItem.STATE2_FLASHLIGHT_FLAG, uid, "flashlight"); mFlashlightOnTimer.stopRunningLocked(elapsedRealtimeMs); } - getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs) - .noteFlashlightTurnedOffLocked(elapsedRealtimeMs); + if (!mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) { + getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs) + .noteFlashlightTurnedOffLocked(elapsedRealtimeMs); + } } @GuardedBy("this") diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java index b25239574071..ba6e4a96ddc4 100644 --- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java +++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java @@ -97,9 +97,15 @@ public class BatteryUsageStatsProvider { mContext.getSystemService(SensorManager.class))); mPowerCalculators.add(new GnssPowerCalculator(mPowerProfile)); mPowerCalculators.add(new CameraPowerCalculator(mPowerProfile)); - mPowerCalculators.add(new FlashlightPowerCalculator(mPowerProfile)); - mPowerCalculators.add(new AudioPowerCalculator(mPowerProfile)); - mPowerCalculators.add(new VideoPowerCalculator(mPowerProfile)); + if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) { + mPowerCalculators.add(new FlashlightPowerCalculator(mPowerProfile)); + } + if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_AUDIO)) { + mPowerCalculators.add(new AudioPowerCalculator(mPowerProfile)); + } + if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_VIDEO)) { + mPowerCalculators.add(new VideoPowerCalculator(mPowerProfile)); + } mPowerCalculators.add(new ScreenPowerCalculator(mPowerProfile)); mPowerCalculators.add(new AmbientDisplayPowerCalculator(mPowerProfile)); mPowerCalculators.add(new IdlePowerCalculator(mPowerProfile)); diff --git a/services/core/java/com/android/server/power/stats/FlashlightPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/FlashlightPowerStatsProcessor.java new file mode 100644 index 000000000000..f7216c9af9d6 --- /dev/null +++ b/services/core/java/com/android/server/power/stats/FlashlightPowerStatsProcessor.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power.stats; + +import android.os.BatteryConsumer; +import android.os.BatteryStats; + +import com.android.internal.os.PowerProfile; + +public class FlashlightPowerStatsProcessor extends BinaryStatePowerStatsProcessor { + public FlashlightPowerStatsProcessor(PowerProfile powerProfile, + PowerStatsUidResolver uidResolver) { + super(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT, uidResolver, + powerProfile.getAveragePower(PowerProfile.POWER_FLASHLIGHT)); + } + + @Override + protected @BinaryState int getBinaryState(BatteryStats.HistoryItem item) { + return (item.states2 & BatteryStats.HistoryItem.STATE2_FLASHLIGHT_FLAG) != 0 + ? STATE_ON + : STATE_OFF; + } +} diff --git a/services/core/java/com/android/server/power/stats/VideoPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/VideoPowerStatsProcessor.java new file mode 100644 index 000000000000..48dac8a8a970 --- /dev/null +++ b/services/core/java/com/android/server/power/stats/VideoPowerStatsProcessor.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power.stats; + +import android.os.BatteryConsumer; +import android.os.BatteryStats; + +import com.android.internal.os.PowerProfile; + +public class VideoPowerStatsProcessor extends BinaryStatePowerStatsProcessor { + public VideoPowerStatsProcessor(PowerProfile powerProfile, + PowerStatsUidResolver uidResolver) { + super(BatteryConsumer.POWER_COMPONENT_VIDEO, uidResolver, + powerProfile.getAveragePower(PowerProfile.POWER_VIDEO)); + } + + @Override + protected @BinaryState int getBinaryState(BatteryStats.HistoryItem item) { + return (item.states2 & BatteryStats.HistoryItem.STATE2_VIDEO_ON_FLAG) != 0 + ? STATE_ON + : STATE_OFF; + } +} diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java index 80f1125a4ecf..f70a3ba107e1 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java @@ -150,7 +150,7 @@ public class WallpaperCropper { Rect landscapeCrop = getCrop(rotatedDisplaySize, bitmapSize, suggestedCrops, rtl); landscapeCrop = noParallax(landscapeCrop, rotatedDisplaySize, bitmapSize, rtl); // compute the crop on portrait at the center of the landscape crop - crop = getAdjustedCrop(landscapeCrop, bitmapSize, displaySize, false, ADD); + crop = getAdjustedCrop(landscapeCrop, bitmapSize, displaySize, false, rtl, ADD); // add some parallax (until the border of the landscape crop without parallax) if (rtl) { @@ -160,7 +160,7 @@ public class WallpaperCropper { } } - return getAdjustedCrop(crop, bitmapSize, displaySize, true, ADD); + return getAdjustedCrop(crop, bitmapSize, displaySize, true, rtl, ADD); } // If any suggested crop is invalid, fallback to case 1 @@ -176,7 +176,7 @@ public class WallpaperCropper { // Case 2: if the orientation exists in the suggested crops, adjust the suggested crop Rect suggestedCrop = suggestedCrops.get(orientation); if (suggestedCrop != null) { - return getAdjustedCrop(suggestedCrop, bitmapSize, displaySize, true, ADD); + return getAdjustedCrop(suggestedCrop, bitmapSize, displaySize, true, rtl, ADD); } // Case 3: if we have the 90° rotated orientation in the suggested crops, reuse it and @@ -188,7 +188,7 @@ public class WallpaperCropper { if (suggestedCrop != null) { // only keep the visible part (without parallax) Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl); - return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, BALANCE); + return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, BALANCE); } // Case 4: if the device is a foldable, if we're looking for a folded orientation and have @@ -200,13 +200,13 @@ public class WallpaperCropper { // compute the visible part (without parallax) of the unfolded screen Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl); // compute the folded crop, at the center of the crop of the unfolded screen - Rect res = getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, REMOVE); + Rect res = getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, REMOVE); // if we removed some width, add it back to add a parallax effect if (res.width() < adjustedCrop.width()) { if (rtl) res.left = Math.min(res.left, adjustedCrop.left); else res.right = Math.max(res.right, adjustedCrop.right); // use getAdjustedCrop(parallax=true) to make sure we don't exceed MAX_PARALLAX - res = getAdjustedCrop(res, bitmapSize, displaySize, true, ADD); + res = getAdjustedCrop(res, bitmapSize, displaySize, true, rtl, ADD); } return res; } @@ -220,7 +220,7 @@ public class WallpaperCropper { if (suggestedCrop != null) { // only keep the visible part (without parallax) Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl); - return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, ADD); + return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, ADD); } // Case 6: for a foldable device, try to combine case 3 + case 4 or 5: @@ -255,7 +255,7 @@ public class WallpaperCropper { @VisibleForTesting static Rect noParallax(Rect crop, Point displaySize, Point bitmapSize, boolean rtl) { if (displaySize == null) return crop; - Rect adjustedCrop = getAdjustedCrop(crop, bitmapSize, displaySize, true, ADD); + Rect adjustedCrop = getAdjustedCrop(crop, bitmapSize, displaySize, true, rtl, ADD); // only keep the visible part (without parallax) float suggestedDisplayRatio = 1f * displaySize.x / displaySize.y; int widthToRemove = (int) (adjustedCrop.width() @@ -272,7 +272,7 @@ public class WallpaperCropper { * Adjust a given crop: * <ul> * <li>If parallax = true, make sure we have a parallax of at most {@link #MAX_PARALLAX}, - * by removing content from both sides if necessary. + * by removing content from the right (or left if RTL) if necessary. * <li>If parallax = false, make sure we do not have additional width for parallax. If we * have additional width for parallax, remove half of the additional width on both sides. * <li>Make sure the crop fills the screen, i.e. that the width/height ratio of the crop @@ -282,7 +282,7 @@ public class WallpaperCropper { */ @VisibleForTesting static Rect getAdjustedCrop(Rect crop, Point bitmapSize, Point screenSize, - boolean parallax, int mode) { + boolean parallax, boolean rtl, int mode) { Rect adjustedCrop = new Rect(crop); float cropRatio = ((float) crop.width()) / crop.height(); float screenRatio = ((float) screenSize.x) / screenSize.y; @@ -297,7 +297,8 @@ public class WallpaperCropper { Rect rotatedCrop = new Rect(newLeft, newTop, newRight, newBottom); Point rotatedBitmap = new Point(bitmapSize.y, bitmapSize.x); Point rotatedScreen = new Point(screenSize.y, screenSize.x); - Rect rect = getAdjustedCrop(rotatedCrop, rotatedBitmap, rotatedScreen, false, mode); + Rect rect = getAdjustedCrop( + rotatedCrop, rotatedBitmap, rotatedScreen, false, rtl, mode); int resultLeft = rect.top; int resultRight = resultLeft + rect.height(); int resultTop = rotatedBitmap.x - rect.right; @@ -308,8 +309,11 @@ public class WallpaperCropper { if (additionalWidthForParallax > MAX_PARALLAX) { int widthToRemove = (int) Math.ceil( (additionalWidthForParallax - MAX_PARALLAX) * screenRatio * crop.height()); - adjustedCrop.left += widthToRemove / 2; - adjustedCrop.right -= widthToRemove / 2 + widthToRemove % 2; + if (rtl) { + adjustedCrop.left += widthToRemove; + } else { + adjustedCrop.right -= widthToRemove; + } } } else { // Note: the third case when MODE == BALANCE, -W + sqrt(W * H * R), is the width to add diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt index 3bdcd9b0fdcd..161a8168d993 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt @@ -246,24 +246,32 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS } override fun setUidMode(uid: Int, deviceId: String, code: Int, mode: Int): Boolean { + val appId = UserHandle.getAppId(uid) + val userId = UserHandle.getUserId(uid) + val appOpName = AppOpsManager.opToPublicName(code) + if ( Flags.runtimePermissionAppopsMappingEnabled() && code in runtimeAppOpToPermissionNames ) { - Slog.w( - LOG_TAG, - "Cannot set UID mode for runtime permission app op, " + - " callingUid = ${Binder.getCallingUid()}, " + + val oldMode = + service.getState { with(appIdPolicy) { getAppOpMode(appId, userId, appOpName) } } + val wouldHaveChanged = oldMode != mode + val logMessage = + (if (wouldHaveChanged) "Blocked" else "Ignored") + + " setUidMode call for runtime permission app op:" + " uid = $uid," + " code = ${AppOpsManager.opToName(code)}," + - " mode = ${AppOpsManager.modeToName(mode)}", - RuntimeException() - ) + " mode = ${AppOpsManager.modeToName(mode)}," + + " callingUid = ${Binder.getCallingUid()}," + + " oldMode = ${AppOpsManager.modeToName(oldMode)}" + if (wouldHaveChanged) { + Slog.e(LOG_TAG, logMessage, RuntimeException()) + } else { + Slog.w(LOG_TAG, logMessage) + } return false } - val appId = UserHandle.getAppId(uid) - val userId = UserHandle.getUserId(uid) - val appOpName = AppOpsManager.opToPublicName(code) var wasChanged: Boolean service.mutateState { wasChanged = with(appIdPolicy) { setAppOpMode(appId, userId, appOpName, mode) } diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java index 1b0a8d2222b9..f1bf86f2f57c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java @@ -211,9 +211,11 @@ public class WallpaperCropperTest { new Rect(100, 200, bitmapSize.x - 100, bitmapSize.y))) { for (int mode: ALL_MODES) { for (boolean parallax: List.of(true, false)) { - assertThat(WallpaperCropper.getAdjustedCrop( - crop, bitmapSize, displaySize, parallax, mode)) - .isEqualTo(crop); + for (boolean rtl: List.of(true, false)) { + assertThat(WallpaperCropper.getAdjustedCrop( + crop, bitmapSize, displaySize, parallax, rtl, mode)) + .isEqualTo(crop); + } } } } @@ -234,8 +236,11 @@ public class WallpaperCropperTest { Point expectedCropSize = new Point(expectedWidth, 1000); for (int mode: ALL_MODES) { assertThat(WallpaperCropper.getAdjustedCrop( - crop, bitmapSize, displaySize, true, mode)) - .isEqualTo(centerOf(crop, expectedCropSize)); + crop, bitmapSize, displaySize, true, false, mode)) + .isEqualTo(leftOf(crop, expectedCropSize)); + assertThat(WallpaperCropper.getAdjustedCrop( + crop, bitmapSize, displaySize, true, true, mode)) + .isEqualTo(rightOf(crop, expectedCropSize)); } } @@ -254,9 +259,11 @@ public class WallpaperCropperTest { Point bitmapSize = new Point(acceptableWidth, 1000); Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y); for (int mode : ALL_MODES) { - assertThat(WallpaperCropper.getAdjustedCrop( - crop, bitmapSize, displaySize, true, mode)) - .isEqualTo(crop); + for (boolean rtl : List.of(false, true)) { + assertThat(WallpaperCropper.getAdjustedCrop( + crop, bitmapSize, displaySize, true, rtl, mode)) + .isEqualTo(crop); + } } } } @@ -286,9 +293,11 @@ public class WallpaperCropperTest { for (int i = 0; i < crops.size(); i++) { Rect crop = crops.get(i); Rect expectedCrop = expectedAdjustedCrops.get(i); - assertThat(WallpaperCropper.getAdjustedCrop( - crop, bitmapSize, displaySize, false, WallpaperCropper.ADD)) - .isEqualTo(expectedCrop); + for (boolean rtl: List.of(false, true)) { + assertThat(WallpaperCropper.getAdjustedCrop( + crop, bitmapSize, displaySize, false, rtl, WallpaperCropper.ADD)) + .isEqualTo(expectedCrop); + } } } @@ -309,9 +318,11 @@ public class WallpaperCropperTest { Point expectedCropSize = new Point(1000, 1000); for (Rect crop: crops) { - assertThat(WallpaperCropper.getAdjustedCrop( - crop, bitmapSize, displaySize, false, WallpaperCropper.REMOVE)) - .isEqualTo(centerOf(crop, expectedCropSize)); + for (boolean rtl : List.of(false, true)) { + assertThat(WallpaperCropper.getAdjustedCrop( + crop, bitmapSize, displaySize, false, rtl, WallpaperCropper.REMOVE)) + .isEqualTo(centerOf(crop, expectedCropSize)); + } } } @@ -338,14 +349,14 @@ public class WallpaperCropperTest { Rect crop = crops.get(i); Rect expected = expectedAdjustedCrops.get(i); assertThat(WallpaperCropper.getAdjustedCrop( - crop, bitmapSize, displaySize, false, WallpaperCropper.BALANCE)) + crop, bitmapSize, displaySize, false, false, WallpaperCropper.BALANCE)) .isEqualTo(expected); Rect transposedCrop = new Rect(crop.top, crop.left, crop.bottom, crop.right); Rect expectedTransposed = new Rect( expected.top, expected.left, expected.bottom, expected.right); assertThat(WallpaperCropper.getAdjustedCrop(transposedCrop, bitmapSize, - transposedDisplaySize, false, WallpaperCropper.BALANCE)) + transposedDisplaySize, false, false, WallpaperCropper.BALANCE)) .isEqualTo(expectedTransposed); } } @@ -376,9 +387,11 @@ public class WallpaperCropperTest { Point displaySize = displaySizes.get(i); Point expectedCropSize = expectedCropSizes.get(i); for (boolean rtl : List.of(false, true)) { + Rect expectedCrop = rtl ? rightOf(bitmapRect, expectedCropSize) + : leftOf(bitmapRect, expectedCropSize); assertThat(mWallpaperCropper.getCrop( displaySize, bitmapSize, suggestedCrops, rtl)) - .isEqualTo(centerOf(bitmapRect, expectedCropSize)); + .isEqualTo(expectedCrop); } } } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java index 851cf4a535a2..976cc18127f0 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java @@ -91,7 +91,7 @@ public class BatteryUsageStatsTest { final Parcel parcel = Parcel.obtain(); parcel.writeParcelable(outBatteryUsageStats, 0); - assertThat(parcel.dataSize()).isLessThan(8000); + assertThat(parcel.dataSize()).isLessThan(10000); parcel.setDataPosition(0); diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index bc8f65edaa12..09cb464198b5 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -9842,6 +9842,43 @@ public class CarrierConfigManager { public static final String KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL = "remove_satellite_plmn_in_manual_network_scan_bool"; + + /** @hide */ + @IntDef({ + SATELLITE_DATA_SUPPORT_ONLY_RESTRICTED, + SATELLITE_DATA_SUPPORT_BANDWIDTH_CONSTRAINED, + SATELLITE_DATA_SUPPORT_ALL, + }) + public @interface SATELLITE_DATA_SUPPORT_MODE {} + + /** + * Doesn't support unrestricted traffic on satellite network. + * @hide + */ + public static final int SATELLITE_DATA_SUPPORT_ONLY_RESTRICTED = 0; + /** + * Support unrestricted but bandwidth_constrained traffic on satellite network. + * @hide + */ + public static final int SATELLITE_DATA_SUPPORT_BANDWIDTH_CONSTRAINED = 1; + /** + * Support unrestricted satellite network that serves all traffic. + * @hide + */ + public static final int SATELLITE_DATA_SUPPORT_ALL = 2; + /** + * Indicates what kind of traffic an {@link NetworkCapabilities#NET_CAPABILITY_NOT_RESTRICTED} + * satellite network can possibly support. The network may subject to further + * restrictions such as entitlement etc. + * If no data is allowed on satellite network, exclude + * {@link ApnSetting#INFRASTRUCTURE_SATELLITE} from APN infrastructure_bitmask, and this + * configuration is ignored. + * By default it only supports restricted data. + * @hide + */ + public static final String KEY_SATELLITE_DATA_SUPPORT_MODE_INT = + "satellite_data_support_mode_int"; + /** * Determine whether to override roaming Wi-Fi Calling preference when device is connected to * non-terrestrial network. @@ -11084,6 +11121,8 @@ public class CarrierConfigManager { sDefaults.putInt(KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT, CellSignalStrengthLte.USE_RSRP); sDefaults.putBoolean(KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, true); + sDefaults.putInt(KEY_SATELLITE_DATA_SUPPORT_MODE_INT, + CarrierConfigManager.SATELLITE_DATA_SUPPORT_ONLY_RESTRICTED); sDefaults.putBoolean(KEY_OVERRIDE_WFC_ROAMING_MODE_WHILE_USING_NTN_BOOL, true); sDefaults.putInt(KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT, 7); sDefaults.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false); |