Merge "Fix a concurrent modification in ProximityCheck" into main
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 2f80b30..d455853 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -232,6 +232,7 @@
import com.android.internal.content.ReferrerIntent;
import com.android.internal.os.BinderCallsStats;
import com.android.internal.os.BinderInternal;
+import com.android.internal.os.DebugStore;
import com.android.internal.os.RuntimeInit;
import com.android.internal.os.SafeZipPathValidatorCallback;
import com.android.internal.os.SomeArgs;
@@ -358,6 +359,15 @@
private static final long BINDER_CALLBACK_THROTTLE = 10_100L;
private long mBinderCallbackLast = -1;
+ private static final boolean DEBUG_STORE_ENABLED =
+ com.android.internal.os.Flags.debugStoreEnabled();
+
+ /**
+ * Threshold for identifying long-running looper messages (in milliseconds).
+ * Calculated as 2 seconds multiplied by the hardware timeout multiplier.
+ */
+ private static final long LONG_MESSAGE_THRESHOLD_MS = 2000 * Build.HW_TIMEOUT_MULTIPLIER;
+
/**
* Denotes the sequence number of the process state change for which the main thread needs
* to block until the network rules are updated for it.
@@ -2395,6 +2405,12 @@
}
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
+ long debugStoreId = -1;
+ // By default, log all long messages when the debug store is enabled,
+ // unless this is overridden for certain message types, for which we have
+ // more granular debug store logging.
+ boolean shouldLogLongMessage = DEBUG_STORE_ENABLED;
+ final long messageStartUptimeMs = SystemClock.uptimeMillis();
switch (msg.what) {
case BIND_APPLICATION:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
@@ -2419,24 +2435,61 @@
"broadcastReceiveComp");
}
}
- handleReceiver((ReceiverData)msg.obj);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ ReceiverData receiverData = (ReceiverData) msg.obj;
+ if (DEBUG_STORE_ENABLED) {
+ debugStoreId =
+ DebugStore.recordBroadcastHandleReceiver(receiverData.intent);
+ }
+
+ try {
+ handleReceiver(receiverData);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ if (DEBUG_STORE_ENABLED) {
+ DebugStore.recordEventEnd(debugStoreId);
+ shouldLogLongMessage = false;
+ }
+ }
break;
case CREATE_SERVICE:
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
("serviceCreate: " + String.valueOf(msg.obj)));
}
- handleCreateService((CreateServiceData)msg.obj);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ CreateServiceData createServiceData = (CreateServiceData) msg.obj;
+ if (DEBUG_STORE_ENABLED) {
+ debugStoreId = DebugStore.recordServiceCreate(createServiceData.info);
+ }
+
+ try {
+ handleCreateService(createServiceData);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ if (DEBUG_STORE_ENABLED) {
+ DebugStore.recordEventEnd(debugStoreId);
+ shouldLogLongMessage = false;
+ }
+ }
break;
case BIND_SERVICE:
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind: "
+ String.valueOf(msg.obj));
}
- handleBindService((BindServiceData)msg.obj);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ BindServiceData bindData = (BindServiceData) msg.obj;
+ if (DEBUG_STORE_ENABLED) {
+ debugStoreId =
+ DebugStore.recordServiceBind(bindData.rebind, bindData.intent);
+ }
+ try {
+ handleBindService(bindData);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ if (DEBUG_STORE_ENABLED) {
+ DebugStore.recordEventEnd(debugStoreId);
+ shouldLogLongMessage = false;
+ }
+ }
break;
case UNBIND_SERVICE:
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
@@ -2452,8 +2505,21 @@
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
("serviceStart: " + String.valueOf(msg.obj)));
}
- handleServiceArgs((ServiceArgsData)msg.obj);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ ServiceArgsData serviceData = (ServiceArgsData) msg.obj;
+ if (DEBUG_STORE_ENABLED) {
+ debugStoreId = DebugStore.recordServiceOnStart(serviceData.startId,
+ serviceData.flags, serviceData.args);
+ }
+
+ try {
+ handleServiceArgs(serviceData);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ if (DEBUG_STORE_ENABLED) {
+ DebugStore.recordEventEnd(debugStoreId);
+ shouldLogLongMessage = false;
+ }
+ }
break;
case STOP_SERVICE:
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
@@ -2649,11 +2715,17 @@
handleFinishInstrumentationWithoutRestart();
break;
}
+ long messageElapsedTimeMs = SystemClock.uptimeMillis() - messageStartUptimeMs;
Object obj = msg.obj;
if (obj instanceof SomeArgs) {
((SomeArgs) obj).recycle();
}
if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
+ if (shouldLogLongMessage
+ && messageElapsedTimeMs > LONG_MESSAGE_THRESHOLD_MS) {
+ DebugStore.recordLongLooperMessage(msg.what, msg.getTarget().getClass().getName(),
+ messageElapsedTimeMs);
+ }
}
}
diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java
index a07f620..a6d3f9d 100644
--- a/core/java/android/app/AppCompatTaskInfo.java
+++ b/core/java/android/app/AppCompatTaskInfo.java
@@ -16,6 +16,8 @@
package android.app;
+import static android.app.TaskInfo.PROPERTY_VALUE_UNSET;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
@@ -76,25 +78,37 @@
* If {@link #isLetterboxDoubleTapEnabled} it contains the current letterbox vertical position
* or {@link TaskInfo#PROPERTY_VALUE_UNSET} otherwise.
*/
- public int topActivityLetterboxVerticalPosition;
+ public int topActivityLetterboxVerticalPosition = PROPERTY_VALUE_UNSET;
/**
* If {@link #isLetterboxDoubleTapEnabled} it contains the current letterbox vertical position
* or {@link TaskInfo#PROPERTY_VALUE_UNSET} otherwise.
*/
- public int topActivityLetterboxHorizontalPosition;
+ public int topActivityLetterboxHorizontalPosition = PROPERTY_VALUE_UNSET;
/**
* If {@link #isLetterboxDoubleTapEnabled} it contains the current width of the letterboxed
* activity or {@link TaskInfo#PROPERTY_VALUE_UNSET} otherwise.
*/
- public int topActivityLetterboxWidth;
+ public int topActivityLetterboxWidth = PROPERTY_VALUE_UNSET;
/**
* If {@link #isLetterboxDoubleTapEnabled} it contains the current height of the letterboxed
* activity or {@link TaskInfo#PROPERTY_VALUE_UNSET} otherwise.
*/
- public int topActivityLetterboxHeight;
+ public int topActivityLetterboxHeight = PROPERTY_VALUE_UNSET;
+
+ /**
+ * Contains the current app height of the letterboxed activity if available or
+ * {@link TaskInfo#PROPERTY_VALUE_UNSET} otherwise.
+ */
+ public int topActivityLetterboxAppHeight = PROPERTY_VALUE_UNSET;
+
+ /**
+ * Contains the current app width of the letterboxed activity if available or
+ * {@link TaskInfo#PROPERTY_VALUE_UNSET} otherwise.
+ */
+ public int topActivityLetterboxAppWidth = PROPERTY_VALUE_UNSET;
/**
* Stores camera-related app compat information about a particular Task.
@@ -162,6 +176,8 @@
&& topActivityLetterboxVerticalPosition == that.topActivityLetterboxVerticalPosition
&& topActivityLetterboxWidth == that.topActivityLetterboxWidth
&& topActivityLetterboxHeight == that.topActivityLetterboxHeight
+ && topActivityLetterboxAppWidth == that.topActivityLetterboxAppWidth
+ && topActivityLetterboxAppHeight == that.topActivityLetterboxAppHeight
&& topActivityLetterboxHorizontalPosition
== that.topActivityLetterboxHorizontalPosition
&& isUserFullscreenOverrideEnabled == that.isUserFullscreenOverrideEnabled
@@ -188,6 +204,8 @@
== that.topActivityLetterboxHorizontalPosition
&& topActivityLetterboxWidth == that.topActivityLetterboxWidth
&& topActivityLetterboxHeight == that.topActivityLetterboxHeight
+ && topActivityLetterboxAppWidth == that.topActivityLetterboxAppWidth
+ && topActivityLetterboxAppHeight == that.topActivityLetterboxAppHeight
&& isUserFullscreenOverrideEnabled == that.isUserFullscreenOverrideEnabled
&& isSystemFullscreenOverrideEnabled == that.isSystemFullscreenOverrideEnabled
&& cameraCompatTaskInfo.equalsForCompatUi(that.cameraCompatTaskInfo);
@@ -208,6 +226,8 @@
topActivityLetterboxHorizontalPosition = source.readInt();
topActivityLetterboxWidth = source.readInt();
topActivityLetterboxHeight = source.readInt();
+ topActivityLetterboxAppWidth = source.readInt();
+ topActivityLetterboxAppHeight = source.readInt();
isUserFullscreenOverrideEnabled = source.readBoolean();
isSystemFullscreenOverrideEnabled = source.readBoolean();
cameraCompatTaskInfo = source.readTypedObject(CameraCompatTaskInfo.CREATOR);
@@ -229,6 +249,8 @@
dest.writeInt(topActivityLetterboxHorizontalPosition);
dest.writeInt(topActivityLetterboxWidth);
dest.writeInt(topActivityLetterboxHeight);
+ dest.writeInt(topActivityLetterboxAppWidth);
+ dest.writeInt(topActivityLetterboxAppHeight);
dest.writeBoolean(isUserFullscreenOverrideEnabled);
dest.writeBoolean(isSystemFullscreenOverrideEnabled);
dest.writeTypedObject(cameraCompatTaskInfo, flags);
@@ -250,6 +272,8 @@
+ topActivityLetterboxHorizontalPosition
+ " topActivityLetterboxWidth=" + topActivityLetterboxWidth
+ " topActivityLetterboxHeight=" + topActivityLetterboxHeight
+ + " topActivityLetterboxAppWidth=" + topActivityLetterboxAppWidth
+ + " topActivityLetterboxAppHeight=" + topActivityLetterboxAppHeight
+ " isUserFullscreenOverrideEnabled=" + isUserFullscreenOverrideEnabled
+ " isSystemFullscreenOverrideEnabled=" + isSystemFullscreenOverrideEnabled
+ " cameraCompatTaskInfo=" + cameraCompatTaskInfo.toString()
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 42da7e9..d89ffc9 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -837,8 +837,15 @@
* components.</p>
* <p>Any change to the exemptions will only be applied for new activity launches.</p>
*
+ * @param componentName the component name to be exempt from the activity launch policy.
+ * @param displayId the ID of the display, for which to apply the exemption. The display
+ * must belong to the virtual device.
+ * @throws IllegalArgumentException if the specified display does not belong to the virtual
+ * device.
+ *
* @see #removeActivityPolicyExemption
* @see #setDevicePolicy
+ * @see Display#getDisplayId
*/
@FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API)
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@@ -861,8 +868,15 @@
* <p>Note that changing the activity launch policy will clear current set of exempt
* components.</p>
*
+ * @param componentName the component name to be removed from the exemption list.
+ * @param displayId the ID of the display, for which to apply the exemption. The display
+ * must belong to the virtual device.
+ * @throws IllegalArgumentException if the specified display does not belong to the virtual
+ * device.
+ *
* @see #addActivityPolicyExemption
* @see #setDevicePolicy
+ * @see Display#getDisplayId
*/
@FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API)
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
index d7195a7..964a8be 100644
--- a/core/java/android/content/BroadcastReceiver.java
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -34,6 +34,8 @@
import android.util.Log;
import android.util.Slog;
+import com.android.internal.os.DebugStore;
+
/**
* Base class for code that receives and handles broadcast intents sent by
* {@link android.content.Context#sendBroadcast(Intent)}.
@@ -55,6 +57,9 @@
private PendingResult mPendingResult;
private boolean mDebugUnregister;
+ private static final boolean DEBUG_STORE_ENABLED =
+ com.android.internal.os.Flags.debugStoreEnabled();
+
/**
* State for a result that is pending for a broadcast receiver. Returned
* by {@link BroadcastReceiver#goAsync() goAsync()}
@@ -255,6 +260,9 @@
"PendingResult#finish#ClassName:" + mReceiverClassName,
1);
}
+ if (DEBUG_STORE_ENABLED) {
+ DebugStore.recordFinish(mReceiverClassName);
+ }
if (mType == TYPE_COMPONENT) {
final IActivityManager mgr = ActivityManager.getService();
@@ -433,7 +441,9 @@
public final PendingResult goAsync() {
PendingResult res = mPendingResult;
mPendingResult = null;
-
+ if (DEBUG_STORE_ENABLED) {
+ DebugStore.recordGoAsync(getClass().getName());
+ }
if (res != null && Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
res.mReceiverClassName = getClass().getName();
Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER,
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index d9b0e6d..7c2edd7 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -237,6 +237,8 @@
bug: "307327678"
}
+# This flag is enabled since V but not a MUST requirement in CDD yet, so it needs to stay around
+# for now and any code working with it should keep checking the flag.
flag {
name: "restrict_nonpreloads_system_shareduids"
namespace: "package_manager_service"
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 5ef597d..3fe063d 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -91,6 +91,8 @@
bug: "283989236"
}
+# This flag is enabled since V but not a MUST requirement in CDD yet, so it needs to stay around
+# for now and any code working with it should keep checking the flag.
flag {
name: "signature_permission_allowlist_enabled"
is_fixed_read_only: true
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index 910c462..2669391 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -1240,7 +1240,10 @@
return "invalidState{" + state + "}";
}
- private String peopleTypeToString(@PeopleType int peopleType) {
+ /**
+ * @hide
+ */
+ public static String peopleTypeToString(@PeopleType int peopleType) {
switch (peopleType) {
case PEOPLE_TYPE_ANYONE:
return "anyone";
diff --git a/core/java/android/view/SurfaceControlRegistry.java b/core/java/android/view/SurfaceControlRegistry.java
index a806bd2..121c01b 100644
--- a/core/java/android/view/SurfaceControlRegistry.java
+++ b/core/java/android/view/SurfaceControlRegistry.java
@@ -73,7 +73,7 @@
}
// Sort entries by time registered when dumping
// TODO: Or should it sort by name?
- entries.sort((o1, o2) -> (int) (o1.getValue() - o2.getValue()));
+ entries.sort((o1, o2) -> Long.compare(o1.getValue(), o2.getValue()));
final int size = Math.min(entries.size(), limit);
pw.println("SurfaceControlRegistry");
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 2ac5873..4ab6758 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -973,8 +973,12 @@
@GuardedBy("mH")
private void setCurrentRootViewLocked(ViewRootImpl rootView) {
+ final boolean wasEmpty = mCurRootView == null;
mImeDispatcher.switchRootView(mCurRootView, rootView);
mCurRootView = rootView;
+ if (wasEmpty && mCurRootView != null) {
+ mImeDispatcher.updateReceivingDispatcher(mCurRootView.getOnBackInvokedDispatcher());
+ }
}
}
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
index ce1f986..771dc7a 100644
--- a/core/java/android/window/ImeOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -27,10 +27,12 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.util.Log;
+import android.util.Pair;
import android.view.ViewRootImpl;
import com.android.internal.annotations.VisibleForTesting;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.function.Consumer;
@@ -58,7 +60,7 @@
// The handler to run callbacks on. This should be on the same thread
// the ViewRootImpl holding IME's WindowOnBackInvokedDispatcher is created on.
private Handler mHandler;
-
+ private final ArrayDeque<Pair<Integer, Bundle>> mQueuedReceive = new ArrayDeque<>();
public ImeOnBackInvokedDispatcher(Handler handler) {
mResultReceiver = new ResultReceiver(handler) {
@Override
@@ -66,11 +68,22 @@
WindowOnBackInvokedDispatcher dispatcher = getReceivingDispatcher();
if (dispatcher != null) {
receive(resultCode, resultData, dispatcher);
+ } else {
+ mQueuedReceive.add(new Pair<>(resultCode, resultData));
}
}
};
}
+ /** Set receiving dispatcher to consume queued receiving events. */
+ public void updateReceivingDispatcher(@NonNull WindowOnBackInvokedDispatcher dispatcher) {
+ while (!mQueuedReceive.isEmpty()) {
+ final Pair<Integer, Bundle> queuedMessage = mQueuedReceive.poll();
+ receive(queuedMessage.first, queuedMessage.second, dispatcher);
+ }
+ }
+
+
void setHandler(@NonNull Handler handler) {
mHandler = handler;
}
@@ -198,6 +211,7 @@
}
}
mImeCallbacks.clear();
+ mQueuedReceive.clear();
}
@VisibleForTesting(visibility = PACKAGE)
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 80a0102..d5746e5 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -94,14 +94,6 @@
}
flag {
- name: "activity_snapshot_by_default"
- namespace: "systemui"
- description: "Enable record activity snapshot by default"
- bug: "259497289"
- is_fixed_read_only: true
-}
-
-flag {
name: "supports_multi_instance_system_ui"
is_exported: true
namespace: "multitasking"
diff --git a/core/java/com/android/internal/os/DebugStore.java b/core/java/com/android/internal/os/DebugStore.java
new file mode 100644
index 0000000..4c45fee
--- /dev/null
+++ b/core/java/com/android/internal/os/DebugStore.java
@@ -0,0 +1,247 @@
+/*
+ * 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.os;
+
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Intent;
+import android.content.pm.ServiceInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+
+/**
+ * The DebugStore class provides methods for recording various debug events related to service
+ * lifecycle, broadcast receivers and others.
+ * The DebugStore class facilitates debugging ANR issues by recording time-stamped events
+ * related to service lifecycles, broadcast receivers, and other framework operations. It logs
+ * the start and end times of operations within the ANR timer scope called by framework,
+ * enabling pinpointing of methods and events contributing to ANRs.
+ *
+ * Usage currently includes recording service starts, binds, and asynchronous operations initiated
+ * by broadcast receivers, providing a granular view of system behavior that facilitates
+ * identifying performance bottlenecks and optimizing issue resolution.
+ *
+ * @hide
+ */
+public class DebugStore {
+ private static DebugStoreNative sDebugStoreNative = new DebugStoreNativeImpl();
+
+ @UnsupportedAppUsage
+ @VisibleForTesting
+ public static void setDebugStoreNative(DebugStoreNative nativeImpl) {
+ sDebugStoreNative = nativeImpl;
+ }
+ /**
+ * Records the start of a service.
+ *
+ * @param startId The start ID of the service.
+ * @param flags Additional flags for the service start.
+ * @param intent The Intent associated with the service start.
+ * @return A unique ID for the recorded event.
+ */
+ @UnsupportedAppUsage
+ public static long recordServiceOnStart(int startId, int flags, @Nullable Intent intent) {
+ return sDebugStoreNative.beginEvent(
+ "SvcStart",
+ List.of(
+ "stId",
+ String.valueOf(startId),
+ "flg",
+ Integer.toHexString(flags),
+ "act",
+ Objects.toString(intent != null ? intent.getAction() : null),
+ "comp",
+ Objects.toString(intent != null ? intent.getComponent() : null),
+ "pkg",
+ Objects.toString(intent != null ? intent.getPackage() : null)));
+ }
+
+ /**
+ * Records the creation of a service.
+ *
+ * @param serviceInfo Information about the service being created.
+ * @return A unique ID for the recorded event.
+ */
+ @UnsupportedAppUsage
+ public static long recordServiceCreate(@Nullable ServiceInfo serviceInfo) {
+ return sDebugStoreNative.beginEvent(
+ "SvcCreate",
+ List.of(
+ "name",
+ Objects.toString(serviceInfo != null ? serviceInfo.name : null),
+ "pkg",
+ Objects.toString(serviceInfo != null ? serviceInfo.packageName : null)));
+ }
+
+ /**
+ * Records the binding of a service.
+ *
+ * @param isRebind Indicates whether the service is being rebound.
+ * @param intent The Intent associated with the service binding.
+ * @return A unique identifier for the recorded event.
+ */
+ @UnsupportedAppUsage
+ public static long recordServiceBind(boolean isRebind, @Nullable Intent intent) {
+ return sDebugStoreNative.beginEvent(
+ "SvcBind",
+ List.of(
+ "rebind",
+ String.valueOf(isRebind),
+ "act",
+ Objects.toString(intent != null ? intent.getAction() : null),
+ "cmp",
+ Objects.toString(intent != null ? intent.getComponent() : null),
+ "pkg",
+ Objects.toString(intent != null ? intent.getPackage() : null)));
+ }
+
+ /**
+ * Records an asynchronous operation initiated by a broadcast receiver through calling GoAsync.
+ *
+ * @param receiverClassName The class name of the broadcast receiver.
+ */
+ @UnsupportedAppUsage
+ public static void recordGoAsync(String receiverClassName) {
+ sDebugStoreNative.recordEvent(
+ "GoAsync",
+ List.of(
+ "tname",
+ Thread.currentThread().getName(),
+ "tid",
+ String.valueOf(Thread.currentThread().getId()),
+ "rcv",
+ Objects.toString(receiverClassName)));
+ }
+
+ /**
+ * Records the completion of a broadcast operation through calling Finish.
+ *
+ * @param receiverClassName The class of the broadcast receiver that completed the operation.
+ */
+ @UnsupportedAppUsage
+ public static void recordFinish(String receiverClassName) {
+ sDebugStoreNative.recordEvent(
+ "Finish",
+ List.of(
+ "tname",
+ Thread.currentThread().getName(),
+ "tid",
+ String.valueOf(Thread.currentThread().getId()),
+ "rcv",
+ Objects.toString(receiverClassName)));
+ }
+ /**
+ * Records the completion of a long-running looper message.
+ *
+ * @param messageCode The code representing the type of the message.
+ * @param targetClass The FQN of the class that handled the message.
+ * @param elapsedTimeMs The time that was taken to process the message, in milliseconds.
+ */
+ @UnsupportedAppUsage
+ public static void recordLongLooperMessage(int messageCode, String targetClass,
+ long elapsedTimeMs) {
+ sDebugStoreNative.recordEvent(
+ "LooperMsg",
+ List.of(
+ "code",
+ String.valueOf(messageCode),
+ "trgt",
+ Objects.toString(targetClass),
+ "elapsed",
+ String.valueOf(elapsedTimeMs)));
+ }
+
+
+ /**
+ * Records the reception of a broadcast.
+ *
+ * @param intent The Intent associated with the broadcast.
+ * @return A unique ID for the recorded event.
+ */
+ @UnsupportedAppUsage
+ public static long recordBroadcastHandleReceiver(@Nullable Intent intent) {
+ return sDebugStoreNative.beginEvent(
+ "HandleReceiver",
+ List.of(
+ "tname", Thread.currentThread().getName(),
+ "tid", String.valueOf(Thread.currentThread().getId()),
+ "act", Objects.toString(intent != null ? intent.getAction() : null),
+ "cmp", Objects.toString(intent != null ? intent.getComponent() : null),
+ "pkg", Objects.toString(intent != null ? intent.getPackage() : null)));
+ }
+
+ /**
+ * Ends a previously recorded event.
+ *
+ * @param id The unique ID of the event to be ended.
+ */
+ @UnsupportedAppUsage
+ public static void recordEventEnd(long id) {
+ sDebugStoreNative.endEvent(id, Collections.emptyList());
+ }
+
+ /**
+ * An interface for a class that acts as a wrapper for the static native methods
+ * of the Debug Store.
+ *
+ * It allows us to mock static native methods in our tests and should be removed
+ * once mocking static methods becomes easier.
+ */
+ @VisibleForTesting
+ public interface DebugStoreNative {
+ /**
+ * Begins an event with the given name and attributes.
+ */
+ long beginEvent(String eventName, List<String> attributes);
+ /**
+ * Ends an event with the given ID and attributes.
+ */
+ void endEvent(long id, List<String> attributes);
+ /**
+ * Records an event with the given name and attributes.
+ */
+ void recordEvent(String eventName, List<String> attributes);
+ }
+
+ private static class DebugStoreNativeImpl implements DebugStoreNative {
+ @Override
+ public long beginEvent(String eventName, List<String> attributes) {
+ return DebugStore.beginEventNative(eventName, attributes);
+ }
+
+ @Override
+ public void endEvent(long id, List<String> attributes) {
+ DebugStore.endEventNative(id, attributes);
+ }
+
+ @Override
+ public void recordEvent(String eventName, List<String> attributes) {
+ DebugStore.recordEventNative(eventName, attributes);
+ }
+ }
+
+ private static native long beginEventNative(String eventName, List<String> attributes);
+
+ private static native void endEventNative(long id, List<String> attributes);
+
+ private static native void recordEventNative(String eventName, List<String> attributes);
+}
diff --git a/core/java/com/android/internal/os/flags.aconfig b/core/java/com/android/internal/os/flags.aconfig
index 2ad6651..c7117e9 100644
--- a/core/java/com/android/internal/os/flags.aconfig
+++ b/core/java/com/android/internal/os/flags.aconfig
@@ -19,4 +19,12 @@
metadata {
purpose: PURPOSE_BUGFIX
}
+}
+
+flag {
+ name: "debug_store_enabled"
+ namespace: "stability"
+ description: "If the debug store is enabled."
+ bug: "314735374"
+ is_fixed_read_only: true
}
\ No newline at end of file
diff --git a/core/java/com/android/internal/widget/OWNERS b/core/java/com/android/internal/widget/OWNERS
index cf2f202..2d1c2f0 100644
--- a/core/java/com/android/internal/widget/OWNERS
+++ b/core/java/com/android/internal/widget/OWNERS
@@ -3,7 +3,9 @@
per-file ViewPager.java = [email protected]
# LockSettings related
-per-file *LockPattern* = file:/services/core/java/com/android/server/locksettings/OWNERS
+per-file LockPatternChecker.java = file:/services/core/java/com/android/server/locksettings/OWNERS
+per-file LockPatternUtils.java = file:/services/core/java/com/android/server/locksettings/OWNERS
+per-file LockPatternView.java = file:/packages/SystemUI/OWNERS
per-file *LockScreen* = file:/services/core/java/com/android/server/locksettings/OWNERS
per-file *Lockscreen* = file:/services/core/java/com/android/server/locksettings/OWNERS
per-file *LockSettings* = file:/services/core/java/com/android/server/locksettings/OWNERS
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 5c2a167..effbbe2 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
@@ -18,6 +18,11 @@
import com.android.internal.widget.remotecompose.core.operations.NamedVariable;
import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
import com.android.internal.widget.remotecompose.core.operations.Theme;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.ComponentEnd;
+import com.android.internal.widget.remotecompose.core.operations.layout.ComponentStartOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent;
import java.util.ArrayList;
import java.util.HashSet;
@@ -30,6 +35,9 @@
public class CoreDocument {
ArrayList<Operation> mOperations;
+
+ RootLayoutComponent mRootLayoutComponent = null;
+
RemoteComposeState mRemoteComposeState = new RemoteComposeState();
TimeVariables mTimeVariables = new TimeVariables();
// Semantic version of the document
@@ -81,7 +89,6 @@
public void setHeight(int height) {
this.mHeight = height;
mRemoteComposeState.setWindowHeight(height);
-
}
public RemoteComposeBuffer getBuffer() {
@@ -259,10 +266,43 @@
translateOutput[1] = translateY;
}
+ /**
+ * Returns the list of click areas
+ * @return list of click areas in document coordinates
+ */
public Set<ClickAreaRepresentation> getClickAreas() {
return mClickAreas;
}
+ /**
+ * Returns the root layout component
+ * @return returns the root component if it exists, null otherwise
+ */
+ public RootLayoutComponent getRootLayoutComponent() {
+ return mRootLayoutComponent;
+ }
+
+ /**
+ * Invalidate the document for layout measures. This will trigger a layout remeasure pass.
+ */
+ public void invalidateMeasure() {
+ if (mRootLayoutComponent != null) {
+ mRootLayoutComponent.invalidateMeasure();
+ }
+ }
+
+ /**
+ * Returns the component with the given id
+ * @param id component id
+ * @return the component if it exists, null otherwise
+ */
+ public Component getComponent(int id) {
+ if (mRootLayoutComponent != null) {
+ return mRootLayoutComponent.getComponent(id);
+ }
+ return null;
+ }
+
public interface ClickCallbacks {
void click(int id, String metadata);
}
@@ -354,7 +394,54 @@
public void initFromBuffer(RemoteComposeBuffer buffer) {
mOperations = new ArrayList<Operation>();
buffer.inflateFromBuffer(mOperations);
+ mOperations = inflateComponents(mOperations);
mBuffer = buffer;
+ for (Operation op : mOperations) {
+ if (op instanceof RootLayoutComponent) {
+ mRootLayoutComponent = (RootLayoutComponent) op;
+ break;
+ }
+ }
+ if (mRootLayoutComponent != null) {
+ mRootLayoutComponent.assignIds();
+ }
+ }
+
+ /**
+ * Inflate a component tree
+ * @param operations flat list of operations
+ * @return nested list of operations / components
+ */
+ private ArrayList<Operation> inflateComponents(ArrayList<Operation> operations) {
+ Component currentComponent = null;
+ ArrayList<Component> components = new ArrayList<>();
+ ArrayList<Operation> finalOperationsList = new ArrayList<>();
+ ArrayList<Operation> ops = finalOperationsList;
+
+ for (Operation o : operations) {
+ if (o instanceof ComponentStartOperation) {
+ Component component = (Component) o;
+ component.setParent(currentComponent);
+ components.add(component);
+ currentComponent = component;
+ ops.add(currentComponent);
+ ops = currentComponent.getList();
+ } else if (o instanceof ComponentEnd) {
+ if (currentComponent instanceof LayoutComponent) {
+ ((LayoutComponent) currentComponent).inflate();
+ }
+ components.remove(components.size() - 1);
+ if (!components.isEmpty()) {
+ currentComponent = components.get(components.size() - 1);
+ ops = currentComponent.getList();
+ } else {
+ ops = finalOperationsList;
+ }
+ } else {
+ ops.add(o);
+ }
+ }
+ return ops;
}
/**
@@ -559,6 +646,18 @@
context.loadFloat(RemoteContext.ID_WINDOW_WIDTH, getWidth());
context.loadFloat(RemoteContext.ID_WINDOW_HEIGHT, getHeight());
mRepaintNext = context.updateOps();
+ if (mRootLayoutComponent != null) {
+ if (context.mWidth != mRootLayoutComponent.getWidth()
+ || context.mHeight != mRootLayoutComponent.getHeight()) {
+ mRootLayoutComponent.invalidateMeasure();
+ }
+ if (mRootLayoutComponent.needsMeasure()) {
+ mRootLayoutComponent.layout(context);
+ }
+ if (mRootLayoutComponent.doesNeedsRepaint()) {
+ mRepaintNext = 1;
+ }
+ }
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/Operation.java b/core/java/com/android/internal/widget/remotecompose/core/Operation.java
index 7cb9a42..4a8b3d7 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operation.java
@@ -37,4 +37,3 @@
*/
String deepToString(String indent);
}
-
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 4b8dbf6..9cb024b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
@@ -54,6 +54,21 @@
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.layout.ComponentEnd;
+import com.android.internal.widget.remotecompose.core.operations.layout.ComponentStart;
+import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponentContent;
+import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.animation.AnimationSpec;
+import com.android.internal.widget.remotecompose.core.operations.layout.managers.BoxLayout;
+import com.android.internal.widget.remotecompose.core.operations.layout.managers.ColumnLayout;
+import com.android.internal.widget.remotecompose.core.operations.layout.managers.RowLayout;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.BackgroundModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.BorderModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ClipRectModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.RoundedClipRectModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.utilities.IntMap;
import com.android.internal.widget.remotecompose.core.types.BooleanConstant;
import com.android.internal.widget.remotecompose.core.types.IntegerConstant;
@@ -117,6 +132,27 @@
public static final int INTEGER_EXPRESSION = 144;
/////////////////////////////////////////======================
+
+ ////////////////////////////////////////
+ // Layout commands
+ ////////////////////////////////////////
+
+ public static final int LAYOUT_ROOT = 200;
+ public static final int LAYOUT_CONTENT = 201;
+ public static final int LAYOUT_BOX = 202;
+ public static final int LAYOUT_ROW = 203;
+ public static final int LAYOUT_COLUMN = 204;
+ public static final int COMPONENT_START = 2;
+ public static final int COMPONENT_END = 3;
+ public static final int MODIFIER_WIDTH = 16;
+ public static final int MODIFIER_HEIGHT = 67;
+ public static final int MODIFIER_BACKGROUND = 55;
+ public static final int MODIFIER_BORDER = 107;
+ public static final int MODIFIER_PADDING = 58;
+ public static final int MODIFIER_CLIP_RECT = 108;
+ public static final int MODIFIER_ROUNDED_CLIP_RECT = 54;
+ public static final int ANIMATION_SPEC = 14;
+
public static IntMap<CompanionOperation> map = new IntMap<>();
static {
@@ -162,6 +198,26 @@
map.put(DATA_INT, IntegerConstant.COMPANION);
map.put(INTEGER_EXPRESSION, IntegerExpression.COMPANION);
map.put(DATA_BOOLEAN, BooleanConstant.COMPANION);
+
+ // Layout
+
+ map.put(COMPONENT_START, ComponentStart.COMPANION);
+ map.put(COMPONENT_END, ComponentEnd.COMPANION);
+ map.put(ANIMATION_SPEC, AnimationSpec.COMPANION);
+
+ map.put(MODIFIER_WIDTH, WidthModifierOperation.COMPANION);
+ map.put(MODIFIER_HEIGHT, HeightModifierOperation.COMPANION);
+ map.put(MODIFIER_PADDING, PaddingModifierOperation.COMPANION);
+ map.put(MODIFIER_BACKGROUND, BackgroundModifierOperation.COMPANION);
+ map.put(MODIFIER_BORDER, BorderModifierOperation.COMPANION);
+ map.put(MODIFIER_ROUNDED_CLIP_RECT, RoundedClipRectModifierOperation.COMPANION);
+ map.put(MODIFIER_CLIP_RECT, ClipRectModifierOperation.COMPANION);
+
+ map.put(LAYOUT_ROOT, RootLayoutComponent.COMPANION);
+ map.put(LAYOUT_CONTENT, LayoutComponentContent.COMPANION);
+ map.put(LAYOUT_BOX, BoxLayout.COMPANION);
+ map.put(LAYOUT_COLUMN, ColumnLayout.COMPANION);
+ map.put(LAYOUT_ROW, RowLayout.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 6d8a442..665fcb7 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
@@ -23,6 +23,10 @@
public abstract class PaintContext {
protected RemoteContext mContext;
+ public RemoteContext getContext() {
+ return mContext;
+ }
+
public PaintContext(RemoteContext context) {
this.mContext = context;
}
@@ -31,6 +35,28 @@
this.mContext = context;
}
+ /**
+ * convenience function to call matrixSave()
+ */
+ public void save() {
+ matrixSave();
+ }
+
+ /**
+ * convenience function to call matrixRestore()
+ */
+ public void restore() {
+ matrixRestore();
+ }
+
+ /**
+ * convenience function to call matrixSave()
+ */
+ public void saveLayer(float x, float y, float width, float height) {
+ // TODO
+ matrixSave();
+ }
+
public abstract void drawBitmap(int imageId,
int srcLeft, int srcTop, int srcRight, int srcBottom,
int dstLeft, int dstTop, int dstRight, int dstBottom,
@@ -197,8 +223,49 @@
public abstract void clipPath(int pathId, int regionOp);
/**
+ * Clip based ona round rect
+ * @param width
+ * @param height
+ * @param topStart
+ * @param topEnd
+ * @param bottomStart
+ * @param bottomEnd
+ */
+ public abstract void roundedClipRect(float width, float height,
+ float topStart, float topEnd,
+ float bottomStart, float bottomEnd);
+
+ /**
* Reset the paint
*/
public abstract void reset();
+
+ /**
+ * Returns true if the context is in debug mode
+ *
+ * @return true if in debug mode, false otherwise
+ */
+ public boolean isDebug() {
+ return mContext.isDebug();
+ }
+
+ /**
+ * Returns true if layout animations are enabled
+ *
+ * @return true if animations are enabled, false otherwise
+ */
+ public boolean isAnimationEnabled() {
+ return mContext.isAnimationEnabled();
+ }
+
+ /**
+ * Utility function to log comments
+ *
+ * @param content the content to log
+ */
+ public void log(String content) {
+ System.out.println("[LOG] " + content);
+ }
+
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java b/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
index 2f3fe57..4a1ccc9 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
@@ -23,9 +23,11 @@
@Override
public void apply(RemoteContext context) {
- if (context.getMode() == RemoteContext.ContextMode.PAINT
- && context.getPaintContext() != null) {
- paint((PaintContext) context.getPaintContext());
+ if (context.getMode() == RemoteContext.ContextMode.PAINT) {
+ PaintContext paintContext = context.getPaintContext();
+ if (paintContext != null) {
+ paint(paintContext);
+ }
}
}
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 f5f155e..333951b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
@@ -54,6 +54,18 @@
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.layout.ComponentEnd;
+import com.android.internal.widget.remotecompose.core.operations.layout.ComponentStart;
+import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponentContent;
+import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.managers.BoxLayout;
+import com.android.internal.widget.remotecompose.core.operations.layout.managers.ColumnLayout;
+import com.android.internal.widget.remotecompose.core.operations.layout.managers.RowLayout;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.BackgroundModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.BorderModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ClipRectModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.RoundedClipRectModifierOperation;
import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation;
import com.android.internal.widget.remotecompose.core.types.IntegerConstant;
@@ -132,8 +144,9 @@
* @param contentDescription content description of the document
* @param capabilities bitmask indicating needed capabilities (unused for now)
*/
- public void header(int width, int height, String contentDescription, long capabilities) {
- Header.COMPANION.apply(mBuffer, width, height, capabilities);
+ public void header(int width, int height, String contentDescription,
+ float density, long capabilities) {
+ Header.COMPANION.apply(mBuffer, width, height, density, capabilities);
int contentDescriptionId = 0;
if (contentDescription != null) {
contentDescriptionId = addText(contentDescription);
@@ -149,7 +162,7 @@
* @param contentDescription content description of the document
*/
public void header(int width, int height, String contentDescription) {
- header(width, height, contentDescription, 0);
+ header(width, height, contentDescription, 1f, 0);
}
/**
@@ -857,7 +870,7 @@
}
/**
- * Sets the clip based on clip rec
+ * Sets the clip based on clip rect
* @param left
* @param top
* @param right
@@ -1074,5 +1087,128 @@
NamedVariable.COLOR_TYPE, name);
}
+ /**
+ * Add a component start tag
+ * @param type type of component
+ * @param id component id
+ */
+ public void addComponentStart(int type, int id) {
+ switch (type) {
+ case ComponentStart.ROOT_LAYOUT: {
+ RootLayoutComponent.COMPANION.apply(mBuffer);
+ } break;
+ case ComponentStart.LAYOUT_CONTENT: {
+ LayoutComponentContent.COMPANION.apply(mBuffer);
+ } break;
+ case ComponentStart.LAYOUT_BOX: {
+ BoxLayout.COMPANION.apply(mBuffer, id, -1,
+ BoxLayout.CENTER, BoxLayout.CENTER);
+ } break;
+ case ComponentStart.LAYOUT_ROW: {
+ RowLayout.COMPANION.apply(mBuffer, id, -1,
+ RowLayout.START, RowLayout.TOP, 0f);
+ } break;
+ case ComponentStart.LAYOUT_COLUMN: {
+ ColumnLayout.COMPANION.apply(mBuffer, id, -1,
+ ColumnLayout.START, ColumnLayout.TOP, 0f);
+ } break;
+ default:
+ ComponentStart.Companion.apply(mBuffer,
+ type, id, 0f, 0f);
+ }
+ }
+
+ /**
+ * Add a component start tag
+ * @param type type of component
+ */
+ public void addComponentStart(int type) {
+ addComponentStart(type, -1);
+ }
+
+ /**
+ * Add a component end tag
+ */
+ public void addComponentEnd() {
+ ComponentEnd.Companion.apply(mBuffer);
+ }
+
+ /**
+ * Add a background modifier of provided color
+ * @param color the color of the background
+ * @param shape the background shape -- SHAPE_RECTANGLE, SHAPE_CIRCLE
+ */
+ public void addModifierBackground(int color, int shape) {
+ float r = ((color >> 16) & 0xff) / 255.0f;
+ float g = ((color >> 8) & 0xff) / 255.0f;
+ float b = ((color) & 0xff) / 255.0f;
+ float a = ((color >> 24) & 0xff) / 255.0f;
+ BackgroundModifierOperation.COMPANION.apply(mBuffer, 0f, 0f, 0f, 0f,
+ r, g, b, a, shape);
+ }
+
+ /**
+ * Add a border modifier
+ * @param borderWidth the border width
+ * @param borderRoundedCorner the rounded corner radius if the shape is ROUNDED_RECT
+ * @param color the color of the border
+ * @param shape the shape of the border
+ */
+ public void addModifierBorder(float borderWidth, float borderRoundedCorner,
+ int color, int shape) {
+ float r = ((color >> 16) & 0xff) / 255.0f;
+ float g = ((color >> 8) & 0xff) / 255.0f;
+ float b = ((color) & 0xff) / 255.0f;
+ float a = ((color >> 24) & 0xff) / 255.0f;
+ BorderModifierOperation.COMPANION.apply(mBuffer, 0f, 0f, 0f, 0f,
+ borderWidth, borderRoundedCorner, r, g, b, a, shape);
+ }
+
+ /**
+ * Add a padding modifier
+ * @param left left padding
+ * @param top top padding
+ * @param right right padding
+ * @param bottom bottom padding
+ */
+ public void addModifierPadding(float left, float top, float right, float bottom) {
+ PaddingModifierOperation.COMPANION.apply(mBuffer, left, top, right, bottom);
+ }
+
+
+ /**
+ * Sets the clip based on rounded clip rect
+ * @param topStart
+ * @param topEnd
+ * @param bottomStart
+ * @param bottomEnd
+ */
+ public void addRoundClipRectModifier(float topStart, float topEnd,
+ float bottomStart, float bottomEnd) {
+ RoundedClipRectModifierOperation.COMPANION.apply(mBuffer,
+ topStart, topEnd, bottomStart, bottomEnd);
+ }
+
+ public void addClipRectModifier() {
+ ClipRectModifierOperation.COMPANION.apply(mBuffer);
+ }
+
+ public void addBoxStart(int componentId, int animationId,
+ int horizontal, int vertical) {
+ BoxLayout.COMPANION.apply(mBuffer, componentId, animationId,
+ horizontal, vertical);
+ }
+
+ public void addRowStart(int componentId, int animationId,
+ int horizontal, int vertical, float spacedBy) {
+ RowLayout.COMPANION.apply(mBuffer, componentId, animationId,
+ horizontal, vertical, spacedBy);
+ }
+
+ public void addColumnStart(int componentId, int animationId,
+ int horizontal, int vertical, float spacedBy) {
+ ColumnLayout.COMPANION.apply(mBuffer, componentId, animationId,
+ horizontal, vertical, spacedBy);
+ }
}
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 41eeb5b..893dcce 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
@@ -19,6 +19,7 @@
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;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
/**
* Specify an abstract context used to playback RemoteCompose documents
@@ -35,12 +36,26 @@
ContextMode mMode = ContextMode.UNSET;
boolean mDebug = false;
+
private int mTheme = Theme.UNSPECIFIED;
public float mWidth = 0f;
public float mHeight = 0f;
private float mAnimationTime;
+ private boolean mAnimate = true;
+
+ public Component lastComponent;
+ public long currentTime = 0L;
+
+ public boolean isAnimationEnabled() {
+ return mAnimate;
+ }
+
+ public void setAnimationEnabled(boolean value) {
+ mAnimate = value;
+ }
+
/**
* Load a path under an id.
* Paths can be use in clip drawPath and drawTweenPath
@@ -333,9 +348,11 @@
public static final float FLOAT_COMPONENT_HEIGHT = Utils.asNan(ID_COMPONENT_HEIGHT);
// ID_OFFSET_TO_UTC is the offset from UTC in sec (typically / 3600f)
public static final float FLOAT_OFFSET_TO_UTC = Utils.asNan(ID_OFFSET_TO_UTC);
+
///////////////////////////////////////////////////////////////////////////////////////////////
// Click handling
///////////////////////////////////////////////////////////////////////////////////////////////
+
public abstract void addClickArea(
int id,
int contentDescription,
diff --git a/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentationBuilder.java b/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentationBuilder.java
new file mode 100644
index 0000000..ccbcdf6
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentationBuilder.java
@@ -0,0 +1,22 @@
+/*
+ * 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.documentation;
+
+public interface DocumentationBuilder {
+ void add(String value);
+ Operation operation(String category, int id, String name);
+ Operation wipOperation(String category, int id, String name);
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedCompanionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedCompanionOperation.java
new file mode 100644
index 0000000..6a98b78
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedCompanionOperation.java
@@ -0,0 +1,22 @@
+/*
+ * 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.documentation;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+
+public interface DocumentedCompanionOperation extends CompanionOperation {
+ void documentation(DocumentationBuilder doc);
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/documentation/Operation.java b/core/java/com/android/internal/widget/remotecompose/core/documentation/Operation.java
new file mode 100644
index 0000000..643b925
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/documentation/Operation.java
@@ -0,0 +1,151 @@
+/*
+ * 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.documentation;
+
+import java.util.ArrayList;
+
+public class Operation {
+ public static final int LAYOUT = 0;
+ public static final int INT = 0;
+ public static final int FLOAT = 1;
+ public static final int BOOLEAN = 2;
+ public static final int BUFFER = 4;
+ public static final int UTF8 = 5;
+ public static final int BYTE = 6;
+ public static final int VALUE = 7;
+ public static final int LONG = 8;
+
+ String mCategory;
+ int mId;
+ String mName;
+ String mDescription;
+
+ boolean mWIP;
+ String mTextExamples;
+
+ ArrayList<StringPair> mExamples = new ArrayList<>();
+ ArrayList<OperationField> mFields = new ArrayList<>();
+
+ int mExamplesWidth = 100;
+ int mExamplesHeight = 100;
+
+
+ public static String getType(int type) {
+ switch (type) {
+ case (INT): return "INT";
+ case (FLOAT): return "FLOAT";
+ case (BOOLEAN): return "BOOLEAN";
+ case (BUFFER): return "BUFFER";
+ case (UTF8): return "UTF8";
+ case (BYTE): return "BYTE";
+ case (VALUE): return "VALUE";
+ case (LONG): return "LONG";
+ }
+ return "UNKNOWN";
+ }
+
+ public Operation(String category, int id, String name, boolean wip) {
+ mCategory = category;
+ mId = id;
+ mName = name;
+ mWIP = wip;
+ }
+
+ public Operation(String category, int id, String name) {
+ this(category, id, name, false);
+ }
+
+ public ArrayList<OperationField> getFields() {
+ return mFields;
+ }
+
+ public String getCategory() {
+ return mCategory;
+ }
+
+ public int getId() {
+ return mId;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public boolean isWIP() {
+ return mWIP;
+ }
+
+ public int getSizeFields() {
+ int size = 0;
+ for (OperationField field : mFields) {
+ size += field.getSize();
+ }
+ return size;
+ }
+
+ public String getDescription() {
+ return mDescription;
+ }
+
+ public String getTextExamples() {
+ return mTextExamples;
+ }
+
+ public ArrayList<StringPair> getExamples() {
+ return mExamples;
+ }
+
+ public int getExamplesWidth() {
+ return mExamplesWidth;
+ }
+
+ public int getExamplesHeight() {
+ return mExamplesHeight;
+ }
+
+ public Operation field(int type, String name, String description) {
+ mFields.add(new OperationField(type, name, description));
+ return this;
+ }
+
+ public Operation possibleValues(String name, int value) {
+ if (!mFields.isEmpty()) {
+ mFields.get(mFields.size() - 1).possibleValue(name, "" + value);
+ }
+ return this;
+ }
+
+ public Operation description(String description) {
+ mDescription = description;
+ return this;
+ }
+
+ public Operation examples(String examples) {
+ mTextExamples = examples;
+ return this;
+ }
+
+ public Operation exampleImage(String name, String imagePath) {
+ mExamples.add(new StringPair(name, imagePath));
+ return this;
+ }
+
+ public Operation examplesDimension(int width, int height) {
+ mExamplesWidth = width;
+ mExamplesHeight = height;
+ return this;
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/documentation/OperationField.java b/core/java/com/android/internal/widget/remotecompose/core/documentation/OperationField.java
new file mode 100644
index 0000000..fc73f4ed6
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/documentation/OperationField.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.documentation;
+
+import java.util.ArrayList;
+
+public class OperationField {
+ int mType;
+ String mName;
+ String mDescription;
+ ArrayList<StringPair> mPossibleValues = new ArrayList<>();
+
+ public OperationField(int type, String name, String description) {
+ mType = type;
+ mName = name;
+ mDescription = description;
+ }
+ public int getType() {
+ return mType;
+ }
+ public String getName() {
+ return mName;
+ }
+ public String getDescription() {
+ return mDescription;
+ }
+ public ArrayList<StringPair> getPossibleValues() {
+ return mPossibleValues;
+ }
+ public void possibleValue(String name, String value) {
+ mPossibleValues.add(new StringPair(name, value));
+ }
+ public boolean hasEnumeratedValues() {
+ return !mPossibleValues.isEmpty();
+ }
+ public int getSize() {
+ switch (mType) {
+ case (Operation.BYTE) : return 1;
+ case (Operation.INT) : return 4;
+ case (Operation.FLOAT) : return 4;
+ case (Operation.LONG) : return 8;
+ default : return 0;
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/documentation/StringPair.java b/core/java/com/android/internal/widget/remotecompose/core/documentation/StringPair.java
new file mode 100644
index 0000000..787bb54
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/documentation/StringPair.java
@@ -0,0 +1,32 @@
+/*
+ * 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.documentation;
+public class StringPair {
+ String mName;
+ String mValue;
+
+ StringPair(String name, String value) {
+ mName = name;
+ mValue = value;
+ }
+
+ public String getName() {
+ return mName;
+ }
+ public String getValue() {
+ return mValue;
+ }
+}
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
index ec35a16..53a3aa9 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java
@@ -41,10 +41,10 @@
}
};
protected String mName = "DrawRectBase";
- float mX1;
- float mY1;
- float mX2;
- float mY2;
+ protected float mX1;
+ protected float mY1;
+ protected float mX2;
+ protected float mY2;
float mX1Value;
float mY1Value;
float mX2Value;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
index aabed15e..9a1f37b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
@@ -15,12 +15,16 @@
*/
package com.android.internal.widget.remotecompose.core.operations;
-import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import static com.android.internal.widget.remotecompose.core.documentation.Operation.INT;
+import static com.android.internal.widget.remotecompose.core.documentation.Operation.LONG;
+
import com.android.internal.widget.remotecompose.core.Operation;
import com.android.internal.widget.remotecompose.core.Operations;
import com.android.internal.widget.remotecompose.core.RemoteComposeOperation;
import com.android.internal.widget.remotecompose.core.RemoteContext;
import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedCompanionOperation;
import java.util.List;
@@ -41,6 +45,8 @@
int mWidth;
int mHeight;
+
+ float mDensity;
long mCapabilities;
public static final Companion COMPANION = new Companion();
@@ -54,21 +60,23 @@
* @param patchVersion the patch version of the RemoteCompose document API
* @param width the width of the RemoteCompose document
* @param height the height of the RemoteCompose document
+ * @param density the density at which the document was originally created
* @param capabilities bitmask field storing needed capabilities (unused for now)
*/
public Header(int majorVersion, int minorVersion, int patchVersion,
- int width, int height, long capabilities) {
+ int width, int height, float density, long capabilities) {
this.mMajorVersion = majorVersion;
this.mMinorVersion = minorVersion;
this.mPatchVersion = patchVersion;
this.mWidth = width;
this.mHeight = height;
+ this.mDensity = density;
this.mCapabilities = capabilities;
}
@Override
public void write(WireBuffer buffer) {
- COMPANION.apply(buffer, mWidth, mHeight, mCapabilities);
+ COMPANION.apply(buffer, mWidth, mHeight, mDensity, mCapabilities);
}
@Override
@@ -88,7 +96,7 @@
return toString();
}
- public static class Companion implements CompanionOperation {
+ public static class Companion implements DocumentedCompanionOperation {
private Companion() {
}
@@ -102,13 +110,15 @@
return Operations.HEADER;
}
- public void apply(WireBuffer buffer, int width, int height, long capabilities) {
+ public void apply(WireBuffer buffer, int width, int height,
+ float density, long capabilities) {
buffer.start(Operations.HEADER);
buffer.writeInt(MAJOR_VERSION); // major version number of the protocol
buffer.writeInt(MINOR_VERSION); // minor version number of the protocol
buffer.writeInt(PATCH_VERSION); // patch version number of the protocol
buffer.writeInt(width);
buffer.writeInt(height);
+ // buffer.writeFloat(density);
buffer.writeLong(capabilities);
}
@@ -119,10 +129,26 @@
int patchVersion = buffer.readInt();
int width = buffer.readInt();
int height = buffer.readInt();
+ // float density = buffer.readFloat();
+ float density = 1f;
long capabilities = buffer.readLong();
Header header = new Header(majorVersion, minorVersion, patchVersion,
- width, height, capabilities);
+ width, height, density, capabilities);
operations.add(header);
}
+
+ @Override
+ public void documentation(DocumentationBuilder doc) {
+ doc.operation("Protocol Operations", id(), name())
+ .description("Document metadata, containing the version,"
+ + " original size & density, capabilities mask")
+ .field(INT, "MAJOR_VERSION", "Major version")
+ .field(INT, "MINOR_VERSION", "Minor version")
+ .field(INT, "PATCH_VERSION", "Patch version")
+ .field(INT, "WIDTH", "Major version")
+ .field(INT, "HEIGHT", "Major version")
+ // .field(FLOAT, "DENSITY", "Major version")
+ .field(LONG, "CAPABILITIES", "Major version");
+ }
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java
index cbe9c12..f982997 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java
@@ -15,12 +15,15 @@
*/
package com.android.internal.widget.remotecompose.core.operations;
-import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import static com.android.internal.widget.remotecompose.core.documentation.Operation.INT;
+
import com.android.internal.widget.remotecompose.core.Operation;
import com.android.internal.widget.remotecompose.core.Operations;
import com.android.internal.widget.remotecompose.core.RemoteComposeOperation;
import com.android.internal.widget.remotecompose.core.RemoteContext;
import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedCompanionOperation;
import java.util.List;
@@ -70,12 +73,12 @@
return indent + toString();
}
- public static class Companion implements CompanionOperation {
+ public static class Companion implements DocumentedCompanionOperation {
private Companion() {}
@Override
public String name() {
- return "SetTheme";
+ return "Theme";
}
@Override
@@ -93,5 +96,15 @@
int theme = buffer.readInt();
operations.add(new Theme(theme));
}
+
+ @Override
+ public void documentation(DocumentationBuilder doc) {
+ doc.operation("Protocol Operations", id(), name())
+ .description("Set a theme")
+ .field(INT, "THEME", "theme id")
+ .possibleValues("UNSPECIFIED", Theme.UNSPECIFIED)
+ .possibleValues("DARK", Theme.DARK)
+ .possibleValues("LIGHT", Theme.LIGHT);
+ }
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java
new file mode 100644
index 0000000..ee2e11b
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java
@@ -0,0 +1,473 @@
+/*
+ * 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.layout;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+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.WireBuffer;
+import com.android.internal.widget.remotecompose.core.operations.layout.animation.AnimateMeasure;
+import com.android.internal.widget.remotecompose.core.operations.layout.animation.AnimationSpec;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.Measurable;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers;
+import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.ArrayList;
+
+/**
+ * Generic Component class
+ */
+public class Component extends PaintOperation implements Measurable {
+
+ protected int mComponentId = -1;
+ protected float mX;
+ protected float mY;
+ protected float mWidth;
+ protected float mHeight;
+ protected Component mParent;
+ protected int mAnimationId = -1;
+ public Visibility mVisibility = Visibility.VISIBLE;
+ public ArrayList<Operation> mList = new ArrayList<>();
+ public PaintOperation mPreTranslate;
+ public boolean mNeedsMeasure = true;
+ public boolean mNeedsRepaint = false;
+ public AnimateMeasure mAnimateMeasure;
+ public AnimationSpec mAnimationSpec = new AnimationSpec();
+ public boolean mFirstLayout = true;
+ PaintBundle mPaint = new PaintBundle();
+
+ public ArrayList<Operation> getList() {
+ return mList;
+ }
+ public float getX() {
+ return mX;
+ }
+ public float getY() {
+ return mY;
+ }
+ public float getWidth() {
+ return mWidth;
+ }
+ public float getHeight() {
+ return mHeight;
+ }
+ public int getComponentId() {
+ return mComponentId;
+ }
+
+ public int getAnimationId() {
+ return mAnimationId;
+ }
+
+ public Component getParent() {
+ return mParent;
+ }
+ public void setX(float value) {
+ mX = value;
+ }
+ public void setY(float value) {
+ mY = value;
+ }
+ public void setWidth(float value) {
+ mWidth = value;
+ }
+ public void setHeight(float value) {
+ mHeight = value;
+ }
+
+ public void setComponentId(int id) {
+ mComponentId = id;
+ }
+
+ public void setAnimationId(int id) {
+ mAnimationId = id;
+ }
+
+ public Component(Component parent, int componentId, int animationId,
+ float x, float y, float width, float height) {
+ this.mComponentId = componentId;
+ this.mX = x;
+ this.mY = y;
+ this.mWidth = width;
+ this.mHeight = height;
+ this.mParent = parent;
+ this.mAnimationId = animationId;
+ }
+
+ public Component(int componentId, float x, float y, float width, float height,
+ Component parent) {
+ this(parent, componentId, -1, x, y, width, height);
+ }
+
+ public Component(Component component) {
+ this(component.mParent, component.mComponentId, component.mAnimationId,
+ component.mX, component.mY, component.mWidth, component.mHeight
+ );
+ mList.addAll(component.mList);
+ finalizeCreation();
+ }
+
+ public void finalizeCreation() {
+ for (Operation op : mList) {
+ if (op instanceof Component) {
+ ((Component) op).mParent = this;
+ }
+ if (op instanceof AnimationSpec) {
+ mAnimationSpec = (AnimationSpec) op;
+ mAnimationId = mAnimationSpec.getAnimationId();
+ }
+ }
+ }
+
+ @Override
+ public boolean needsMeasure() {
+ return mNeedsMeasure;
+ }
+
+ public void setParent(Component parent) {
+ mParent = parent;
+ }
+
+ public enum Visibility {
+ VISIBLE,
+ INVISIBLE,
+ GONE
+ }
+
+ public boolean isVisible() {
+ if (mVisibility != Visibility.VISIBLE || mParent == null) {
+ return mVisibility == Visibility.VISIBLE;
+ }
+ if (mParent != null) {
+ return mParent.isVisible();
+ }
+ return true;
+ }
+
+ @Override
+ public void measure(PaintContext context, float minWidth, float maxWidth,
+ float minHeight, float maxHeight, MeasurePass measure) {
+ ComponentMeasure m = measure.get(this);
+ m.setW(mWidth);
+ m.setH(mHeight);
+ }
+
+ @Override
+ public void layout(RemoteContext context, MeasurePass measure) {
+ ComponentMeasure m = measure.get(this);
+ if (!mFirstLayout && context.isAnimationEnabled()) {
+ if (mAnimateMeasure == null) {
+ ComponentMeasure origin = new ComponentMeasure(mComponentId,
+ mX, mY, mWidth, mHeight, mVisibility);
+ ComponentMeasure target = new ComponentMeasure(mComponentId,
+ m.getX(), m.getY(), m.getW(), m.getH(), m.getVisibility());
+ mAnimateMeasure = new AnimateMeasure(context.currentTime, this,
+ origin, target,
+ mAnimationSpec.getMotionDuration(), mAnimationSpec.getVisibilityDuration(),
+ mAnimationSpec.getEnterAnimation(), mAnimationSpec.getExitAnimation(),
+ mAnimationSpec.getMotionEasingType(),
+ mAnimationSpec.getVisibilityEasingType());
+ } else {
+ mAnimateMeasure.updateTarget(m, context.currentTime);
+ }
+ } else {
+ mVisibility = m.getVisibility();
+ }
+ mWidth = m.getW();
+ mHeight = m.getH();
+ setLayoutPosition(m.getX(), m.getY());
+ mFirstLayout = false;
+ }
+
+ public float[] locationInWindow = new float[2];
+
+ public boolean contains(float x, float y) {
+ locationInWindow[0] = 0f;
+ locationInWindow[1] = 0f;
+ getLocationInWindow(locationInWindow);
+ float lx1 = locationInWindow[0];
+ float lx2 = lx1 + mWidth;
+ float ly1 = locationInWindow[1];
+ float ly2 = ly1 + mHeight;
+ return x >= lx1 && x < lx2 && y >= ly1 && y < ly2;
+ }
+
+ public void onClick(float x, float y) {
+ if (!contains(x, y)) {
+ return;
+ }
+ for (Operation op : mList) {
+ if (op instanceof Component) {
+ ((Component) op).onClick(x, y);
+ }
+ if (op instanceof ComponentModifiers) {
+ ((ComponentModifiers) op).onClick(x, y);
+ }
+ }
+ }
+
+ public void getLocationInWindow(float[] value) {
+ value[0] += mX;
+ value[1] += mY;
+ if (mParent != null && mParent instanceof Component) {
+ if (mParent instanceof LayoutComponent) {
+ value[0] += ((LayoutComponent) mParent).getMarginLeft();
+ value[1] += ((LayoutComponent) mParent).getMarginTop();
+ }
+ mParent.getLocationInWindow(value);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "COMPONENT(<" + mComponentId + "> " + getClass().getSimpleName()
+ + ") [" + mX + "," + mY + " - " + mWidth + " x " + mHeight + "] " + textContent()
+ + " Visibility (" + mVisibility + ") ";
+ }
+
+ protected String getSerializedName() {
+ return "COMPONENT";
+ }
+
+ public void serializeToString(int indent, StringSerializer serializer) {
+ serializer.append(indent, getSerializedName() + " [" + mComponentId
+ + ":" + mAnimationId + "] = "
+ + "[" + mX + ", " + mY + ", " + mWidth + ", " + mHeight + "] "
+ + mVisibility
+ // + " [" + mNeedsMeasure + ", " + mNeedsRepaint + "]"
+ );
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ // nothing
+ }
+
+ /**
+ * Returns the top-level RootLayoutComponent
+ */
+ public RootLayoutComponent getRoot() throws Exception {
+ if (this instanceof RootLayoutComponent) {
+ return (RootLayoutComponent) this;
+ }
+ Component p = mParent;
+ while (!(p instanceof RootLayoutComponent)) {
+ if (p == null) {
+ throw new Exception("No RootLayoutComponent found");
+ }
+ p = p.mParent;
+ }
+ return (RootLayoutComponent) p;
+ }
+
+ @Override
+ public String deepToString(String indent) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(indent);
+ builder.append(toString());
+ builder.append("\n");
+ String indent2 = " " + indent;
+ for (Operation op : mList) {
+ builder.append(op.deepToString(indent2));
+ builder.append("\n");
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Mark itself as needing to be remeasured, and walk back up the tree
+ * to mark each parents as well.
+ */
+ public void invalidateMeasure() {
+ needsRepaint();
+ mNeedsMeasure = true;
+ Component p = mParent;
+ while (p != null) {
+ p.mNeedsMeasure = true;
+ p = p.mParent;
+ }
+ }
+
+ public void needsRepaint() {
+ try {
+ getRoot().mNeedsRepaint = true;
+ } catch (Exception e) {
+ // nothing
+ }
+ }
+
+ public String content() {
+ StringBuilder builder = new StringBuilder();
+ for (Operation op : mList) {
+ builder.append("- ");
+ builder.append(op);
+ builder.append("\n");
+ }
+ return builder.toString();
+ }
+
+ public String textContent() {
+ StringBuilder builder = new StringBuilder();
+ for (Operation op : mList) {
+ String letter = "";
+ // if (op instanceof DrawTextRun) {
+ // letter = "[" + ((DrawTextRun) op).text + "]";
+ // }
+ builder.append(letter);
+ }
+ return builder.toString();
+ }
+
+ public void debugBox(Component component, PaintContext context) {
+ float width = component.mWidth;
+ float height = component.mHeight;
+
+ context.savePaint();
+ mPaint.reset();
+ mPaint.setColor(0, 0, 255, 255); // Blue color
+ context.applyPaint(mPaint);
+ context.drawLine(0f, 0f, width, 0f);
+ context.drawLine(width, 0f, width, height);
+ context.drawLine(width, height, 0f, height);
+ context.drawLine(0f, height, 0f, 0f);
+ // context.setColor(255, 0, 0, 255)
+ // context.drawLine(0f, 0f, width, height)
+ // context.drawLine(0f, height, width, 0f)
+ context.restorePaint();
+ }
+
+ public void setLayoutPosition(float x, float y) {
+ this.mX = x;
+ this.mY = y;
+ }
+
+ public float getTranslateX() {
+ if (mParent != null) {
+ return mX - mParent.mX;
+ }
+ return 0f;
+ }
+
+ public float getTranslateY() {
+ if (mParent != null) {
+ return mY - mParent.mY;
+ }
+ return 0f;
+ }
+
+ public void paintingComponent(PaintContext context) {
+ if (mPreTranslate != null) {
+ mPreTranslate.paint(context);
+ }
+ context.save();
+ context.translate(mX, mY);
+ if (context.isDebug()) {
+ debugBox(this, context);
+ }
+ for (Operation op : mList) {
+ if (op instanceof PaintOperation) {
+ ((PaintOperation) op).paint(context);
+ }
+ }
+ context.restore();
+ }
+
+ public boolean applyAnimationAsNeeded(PaintContext context) {
+ if (context.isAnimationEnabled() && mAnimateMeasure != null) {
+ mAnimateMeasure.apply(context);
+ needsRepaint();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ if (context.isDebug()) {
+ context.save();
+ context.translate(mX, mY);
+ context.savePaint();
+ mPaint.reset();
+ mPaint.setColor(0, 255, 0, 255); // Green
+ context.applyPaint(mPaint);
+ context.drawLine(0f, 0f, mWidth, 0f);
+ context.drawLine(mWidth, 0f, mWidth, mHeight);
+ context.drawLine(mWidth, mHeight, 0f, mHeight);
+ context.drawLine(0f, mHeight, 0f, 0f);
+ mPaint.setColor(255, 0, 0, 255); // Red
+ context.applyPaint(mPaint);
+ context.drawLine(0f, 0f, mWidth, mHeight);
+ context.drawLine(0f, mHeight, mWidth, 0f);
+ context.restorePaint();
+ context.restore();
+ }
+ if (applyAnimationAsNeeded(context)) {
+ return;
+ }
+ if (mVisibility == Visibility.GONE) {
+ return;
+ }
+ paintingComponent(context);
+ }
+
+ public void getComponents(ArrayList<Component> components) {
+ for (Operation op : mList) {
+ if (op instanceof Component) {
+ components.add((Component) op);
+ }
+ }
+ }
+
+ public int getComponentCount() {
+ int count = 0;
+ for (Operation op : mList) {
+ if (op instanceof Component) {
+ count += 1 + ((Component) op).getComponentCount();
+ }
+ }
+ return count;
+ }
+
+ public int getPaintId() {
+ if (mAnimationId != -1) {
+ return mAnimationId;
+ }
+ return mComponentId;
+ }
+
+ public boolean doesNeedsRepaint() {
+ return mNeedsRepaint;
+ }
+
+ public Component getComponent(int cid) {
+ if (mComponentId == cid || mAnimationId == cid) {
+ return this;
+ }
+ for (Operation c : mList) {
+ if (c instanceof Component) {
+ Component search = ((Component) c).getComponent(cid);
+ if (search != null) {
+ return search;
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java
new file mode 100644
index 0000000..8a523a2
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java
@@ -0,0 +1,82 @@
+/*
+ * 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.layout;
+
+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 com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedCompanionOperation;
+
+import java.util.List;
+
+public class ComponentEnd implements Operation {
+
+ public static final ComponentEnd.Companion COMPANION = new ComponentEnd.Companion();
+
+ @Override
+ public void write(WireBuffer buffer) {
+ Companion.apply(buffer);
+ }
+
+ @Override
+ public String toString() {
+ return "COMPONENT_END";
+ }
+
+ @Override
+ public void apply(RemoteContext context) {
+ // nothing
+ }
+
+ @Override
+ public String deepToString(String indent) {
+ return (indent != null ? indent : "") + toString();
+ }
+
+ public static class Companion implements DocumentedCompanionOperation {
+ @Override
+ public String name() {
+ return "ComponentEnd";
+ }
+
+ @Override
+ public int id() {
+ return Operations.COMPONENT_END;
+ }
+
+ public static void apply(WireBuffer buffer) {
+ buffer.start(Operations.COMPONENT_END);
+ }
+
+ public static int size() {
+ return 1 + 4 + 4 + 4;
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ operations.add(new ComponentEnd());
+ }
+
+ @Override
+ public void documentation(DocumentationBuilder doc) {
+ doc.operation("Layout Operations", id(), name())
+ .description("End tag for components / layouts. This operation marks the end"
+ + "of a component");
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java
new file mode 100644
index 0000000..5cfad25
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java
@@ -0,0 +1,193 @@
+/*
+ * 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.layout;
+
+import static com.android.internal.widget.remotecompose.core.documentation.Operation.FLOAT;
+import static com.android.internal.widget.remotecompose.core.documentation.Operation.INT;
+
+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 com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedCompanionOperation;
+
+import java.util.List;
+
+public class ComponentStart implements ComponentStartOperation {
+
+ public static final ComponentStart.Companion COMPANION = new ComponentStart.Companion();
+
+ int mType = DEFAULT;
+ float mX;
+ float mY;
+ float mWidth;
+ float mHeight;
+ int mComponentId;
+
+ public int getType() {
+ return mType;
+ }
+
+ public float getX() {
+ return mX;
+ }
+
+ public float getY() {
+ return mY;
+ }
+
+ public float getWidth() {
+ return mWidth;
+ }
+
+ public float getHeight() {
+ return mHeight;
+ }
+
+ public int getComponentId() {
+ return mComponentId;
+ }
+
+ public ComponentStart(int type, int componentId, float width, float height) {
+ this.mType = type;
+ this.mComponentId = componentId;
+ this.mX = 0f;
+ this.mY = 0f;
+ this.mWidth = width;
+ this.mHeight = height;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ Companion.apply(buffer, mType, mComponentId, mWidth, mHeight);
+ }
+
+ @Override
+ public String toString() {
+ return "COMPONENT_START (type " + mType + " " + Companion.typeDescription(mType)
+ + ") - (" + mX + ", " + mY + " - " + mWidth + " x " + mHeight + ")";
+ }
+
+ @Override
+ public String deepToString(String indent) {
+ return (indent != null ? indent : "") + toString();
+ }
+
+ @Override
+ public void apply(RemoteContext context) {
+ // nothing
+ }
+
+ public static final int UNKNOWN = -1;
+ public static final int DEFAULT = 0;
+ public static final int ROOT_LAYOUT = 1;
+ public static final int LAYOUT = 2;
+ public static final int LAYOUT_CONTENT = 3;
+ public static final int SCROLL_CONTENT = 4;
+ public static final int BUTTON = 5;
+ public static final int CHECKBOX = 6;
+ public static final int TEXT = 7;
+ public static final int CURVED_TEXT = 8;
+ public static final int STATE_HOST = 9;
+ public static final int CUSTOM = 10;
+ public static final int LOTTIE = 11;
+ public static final int IMAGE = 12;
+ public static final int STATE_BOX_CONTENT = 13;
+ public static final int LAYOUT_BOX = 14;
+ public static final int LAYOUT_ROW = 15;
+ public static final int LAYOUT_COLUMN = 16;
+
+ public static class Companion implements DocumentedCompanionOperation {
+
+
+ public static String typeDescription(int type) {
+ switch (type) {
+ case DEFAULT:
+ return "DEFAULT";
+ case ROOT_LAYOUT:
+ return "ROOT_LAYOUT";
+ case LAYOUT:
+ return "LAYOUT";
+ case LAYOUT_CONTENT:
+ return "CONTENT";
+ case SCROLL_CONTENT:
+ return "SCROLL_CONTENT";
+ case BUTTON:
+ return "BUTTON";
+ case CHECKBOX:
+ return "CHECKBOX";
+ case TEXT:
+ return "TEXT";
+ case CURVED_TEXT:
+ return "CURVED_TEXT";
+ case STATE_HOST:
+ return "STATE_HOST";
+ case LOTTIE:
+ return "LOTTIE";
+ case CUSTOM:
+ return "CUSTOM";
+ case IMAGE:
+ return "IMAGE";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ @Override
+ public String name() {
+ return "ComponentStart";
+ }
+
+ @Override
+ public int id() {
+ return Operations.COMPONENT_START;
+ }
+
+ public static void apply(WireBuffer buffer, int type, int componentId,
+ float width, float height) {
+ buffer.start(Operations.COMPONENT_START);
+ buffer.writeInt(type);
+ buffer.writeInt(componentId);
+ buffer.writeFloat(width);
+ buffer.writeFloat(height);
+ }
+
+ public static int size() {
+ return 1 + 4 + 4 + 4;
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int type = buffer.readInt();
+ int componentId = buffer.readInt();
+ float width = buffer.readFloat();
+ float height = buffer.readFloat();
+ operations.add(new ComponentStart(type, componentId, width, height));
+ }
+
+ @Override
+ public void documentation(DocumentationBuilder doc) {
+ doc.operation("Layout Operations", id(), name())
+ .description("Basic component encapsulating draw commands."
+ + "This is not resizable.")
+ .field(INT, "TYPE", "Type of components")
+ .field(INT, "COMPONENT_ID", "unique id for this component")
+ .field(FLOAT, "WIDTH", "width of the component")
+ .field(FLOAT, "HEIGHT", "height of the component");
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStartOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStartOperation.java
new file mode 100644
index 0000000..67964ef
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStartOperation.java
@@ -0,0 +1,21 @@
+/*
+ * 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.layout;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+
+public interface ComponentStartOperation extends Operation {
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/DecoratorComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/DecoratorComponent.java
new file mode 100644
index 0000000..941666a
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/DecoratorComponent.java
@@ -0,0 +1,27 @@
+/*
+ * 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.layout;
+
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+
+/**
+ * Indicates a lightweight component (without children) that is only laid out and not able to be
+ * measured. Eg borders, background, clips, etc.
+ */
+public interface DecoratorComponent {
+ void layout(RemoteContext context, float width, float height);
+ void onClick(float x, float y);
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java
new file mode 100644
index 0000000..f198c4a
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java
@@ -0,0 +1,220 @@
+/*
+ * 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.layout;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.DimensionModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthModifierOperation;
+
+import java.util.ArrayList;
+
+/**
+ * Component with modifiers and children
+ */
+public class LayoutComponent extends Component {
+
+ protected WidthModifierOperation mWidthModifier = null;
+ protected HeightModifierOperation mHeightModifier = null;
+
+ // Margins
+ protected float mMarginLeft = 0f;
+ protected float mMarginRight = 0f;
+ protected float mMarginTop = 0f;
+ protected float mMarginBottom = 0f;
+
+ protected float mPaddingLeft = 0f;
+ protected float mPaddingRight = 0f;
+ protected float mPaddingTop = 0f;
+ protected float mPaddingBottom = 0f;
+
+ protected ComponentModifiers mComponentModifiers = new ComponentModifiers();
+ protected ArrayList<Component> mChildrenComponents = new ArrayList<>();
+
+ public LayoutComponent(Component parent, int componentId, int animationId,
+ float x, float y, float width, float height) {
+ super(parent, componentId, animationId, x, y, width, height);
+ }
+
+ public float getMarginLeft() {
+ return mMarginLeft;
+ }
+ public float getMarginRight() {
+ return mMarginRight;
+ }
+ public float getMarginTop() {
+ return mMarginTop;
+ }
+ public float getMarginBottom() {
+ return mMarginBottom;
+ }
+
+ public WidthModifierOperation getWidthModifier() {
+ return mWidthModifier;
+ }
+ public HeightModifierOperation getHeightModifier() {
+ return mHeightModifier;
+ }
+
+ public void inflate() {
+ for (Operation op : mList) {
+ if (op instanceof LayoutComponentContent) {
+ ((LayoutComponentContent) op).mParent = this;
+ mChildrenComponents.clear();
+ ((LayoutComponentContent) op).getComponents(mChildrenComponents);
+ if (mChildrenComponents.isEmpty()) {
+ mChildrenComponents.add((Component) op);
+ }
+ } else if (op instanceof ModifierOperation) {
+ mComponentModifiers.add((ModifierOperation) op);
+ } else {
+ // nothing
+ }
+ }
+
+ mList.clear();
+ mList.add(mComponentModifiers);
+ for (Component c : mChildrenComponents) {
+ c.mParent = this;
+ mList.add(c);
+ }
+
+ mX = 0f;
+ mY = 0f;
+ mMarginLeft = 0f;
+ mMarginTop = 0f;
+ mMarginRight = 0f;
+ mMarginBottom = 0f;
+ mPaddingLeft = 0f;
+ mPaddingTop = 0f;
+ mPaddingRight = 0f;
+ mPaddingBottom = 0f;
+
+ boolean applyHorizontalMargin = true;
+ boolean applyVerticalMargin = true;
+ for (Operation op : mComponentModifiers.getList()) {
+ if (op instanceof PaddingModifierOperation) {
+ // We are accumulating padding modifiers to compute the margin
+ // until we hit a dimension; the computed padding for the
+ // content simply accumulate all the padding modifiers.
+ float left = ((PaddingModifierOperation) op).getLeft();
+ float right = ((PaddingModifierOperation) op).getRight();
+ float top = ((PaddingModifierOperation) op).getTop();
+ float bottom = ((PaddingModifierOperation) op).getBottom();
+ if (applyHorizontalMargin) {
+ mMarginLeft += left;
+ mMarginRight += right;
+ }
+ if (applyVerticalMargin) {
+ mMarginTop += top;
+ mMarginBottom += bottom;
+ }
+ mPaddingLeft += left;
+ mPaddingTop += top;
+ mPaddingRight += right;
+ mPaddingBottom += bottom;
+ }
+ if (op instanceof WidthModifierOperation && mWidthModifier == null) {
+ mWidthModifier = (WidthModifierOperation) op;
+ applyHorizontalMargin = false;
+ }
+ if (op instanceof HeightModifierOperation && mHeightModifier == null) {
+ mHeightModifier = (HeightModifierOperation) op;
+ applyVerticalMargin = false;
+ }
+ }
+ if (mWidthModifier == null) {
+ mWidthModifier = new WidthModifierOperation(DimensionModifierOperation.Type.WRAP);
+ }
+ if (mHeightModifier == null) {
+ mHeightModifier = new HeightModifierOperation(DimensionModifierOperation.Type.WRAP);
+ }
+ mWidth = computeModifierDefinedWidth();
+ mHeight = computeModifierDefinedHeight();
+ }
+
+ @Override
+ public String toString() {
+ return "UNKNOWN LAYOUT_COMPONENT";
+ }
+
+ @Override
+ public void paintingComponent(PaintContext context) {
+ context.save();
+ context.translate(mX, mY);
+ mComponentModifiers.paint(context);
+ float tx = mPaddingLeft;
+ float ty = mPaddingTop;
+ context.translate(tx, ty);
+ for (Component child : mChildrenComponents) {
+ child.paint(context);
+ }
+ context.translate(-tx, -ty);
+ context.restore();
+ }
+
+ /**
+ * Traverse the modifiers to compute indicated dimension
+ */
+ public float computeModifierDefinedWidth() {
+ float s = 0f;
+ float e = 0f;
+ float w = 0f;
+ for (Operation c : mComponentModifiers.getList()) {
+ if (c instanceof WidthModifierOperation) {
+ WidthModifierOperation o = (WidthModifierOperation) c;
+ if (o.getType() == DimensionModifierOperation.Type.EXACT) {
+ w = o.getValue();
+ }
+ break;
+ }
+ if (c instanceof PaddingModifierOperation) {
+ PaddingModifierOperation pop = (PaddingModifierOperation) c;
+ s += pop.getLeft();
+ e += pop.getRight();
+ }
+ }
+ return s + w + e;
+ }
+
+ /**
+ * Traverse the modifiers to compute indicated dimension
+ */
+ public float computeModifierDefinedHeight() {
+ float t = 0f;
+ float b = 0f;
+ float h = 0f;
+ for (Operation c : mComponentModifiers.getList()) {
+ if (c instanceof HeightModifierOperation) {
+ HeightModifierOperation o = (HeightModifierOperation) c;
+ if (o.getType() == DimensionModifierOperation.Type.EXACT) {
+ h = o.getValue();
+ }
+ break;
+ }
+ if (c instanceof PaddingModifierOperation) {
+ PaddingModifierOperation pop = (PaddingModifierOperation) c;
+ t += pop.getTop();
+ b += pop.getBottom();
+ }
+ }
+ return t + h + b;
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java
new file mode 100644
index 0000000..769ff6a
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.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.layout;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedCompanionOperation;
+
+import java.util.List;
+
+/**
+ * Represents the content of a LayoutComponent (i.e. the children components)
+ */
+public class LayoutComponentContent extends Component implements ComponentStartOperation {
+
+ public static final LayoutComponentContent.Companion COMPANION =
+ new LayoutComponentContent.Companion();
+
+ public LayoutComponentContent(int componentId, float x, float y,
+ float width, float height, Component parent, int animationId) {
+ super(parent, componentId, animationId, x, y, width, height);
+ }
+
+ public static class Companion implements DocumentedCompanionOperation {
+ @Override
+ public String name() {
+ return "LayoutContent";
+ }
+
+ @Override
+ public int id() {
+ return Operations.LAYOUT_CONTENT;
+ }
+
+ public void apply(WireBuffer buffer) {
+ buffer.start(Operations.LAYOUT_CONTENT);
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ operations.add(new LayoutComponentContent(
+ -1, 0, 0, 0, 0, null, -1));
+ }
+
+ @Override
+ public void documentation(DocumentationBuilder doc) {
+ doc.operation("Layout Operations", id(), name())
+ .description("Container for components. BoxLayout, RowLayout and ColumnLayout "
+ + "expects a LayoutComponentContent as a child, encapsulating the "
+ + "components that needs to be laid out.");
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java
new file mode 100644
index 0000000..dc13768
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java
@@ -0,0 +1,173 @@
+/*
+ * 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.layout;
+
+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.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedCompanionOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.Measurable;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/**
+ * Represents the root layout component. Entry point to the component tree layout/paint.
+ */
+public class RootLayoutComponent extends Component implements ComponentStartOperation {
+
+ public static final RootLayoutComponent.Companion COMPANION =
+ new RootLayoutComponent.Companion();
+
+ int mCurrentId = -1;
+
+ public RootLayoutComponent(int componentId, float x, float y,
+ float width, float height, Component parent, int animationId) {
+ super(parent, componentId, animationId, x, y, width, height);
+ }
+
+ public RootLayoutComponent(int componentId, float x, float y,
+ float width, float height, Component parent) {
+ super(parent, componentId, -1, x, y, width, height);
+ }
+
+ @Override
+ public String toString() {
+ return "ROOT (" + mX + ", " + mY + " - " + mWidth + " x " + mHeight + ") " + mVisibility;
+ }
+
+ @Override
+ public void serializeToString(int indent, StringSerializer serializer) {
+ serializer.append(indent, "ROOT [" + mComponentId + ":" + mAnimationId
+ + "] = [" + mX + ", " + mY + ", " + mWidth + ", " + mHeight + "] " + mVisibility);
+ }
+
+ public int getNextId() {
+ mCurrentId--;
+ return mCurrentId;
+ }
+
+ public void assignIds() {
+ assignId(this);
+ }
+
+ void assignId(Component component) {
+ if (component.mComponentId == -1) {
+ component.mComponentId = getNextId();
+ }
+ for (Operation op : component.mList) {
+ if (op instanceof Component) {
+ assignId((Component) op);
+ }
+ }
+ }
+
+ /**
+ * This will measure then layout the tree of components
+ */
+ public void layout(RemoteContext context) {
+ if (!mNeedsMeasure) {
+ return;
+ }
+ context.lastComponent = this;
+ mWidth = context.mWidth;
+ mHeight = context.mHeight;
+
+ // TODO: reuse MeasurePass
+ MeasurePass measurePass = new MeasurePass();
+ for (Operation op : mList) {
+ if (op instanceof Measurable) {
+ Measurable m = (Measurable) op;
+ m.measure(context.getPaintContext(),
+ 0f, mWidth, 0f, mHeight, measurePass);
+ m.layout(context, measurePass);
+ }
+ }
+ mNeedsMeasure = false;
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ mNeedsRepaint = false;
+ context.getContext().lastComponent = this;
+ context.save();
+
+ if (mParent == null) { // root layout
+ context.clipRect(0f, 0f, mWidth, mHeight);
+ }
+
+ for (Operation op : mList) {
+ if (op instanceof PaintOperation) {
+ ((PaintOperation) op).paint(context);
+ }
+ }
+
+ context.restore();
+ }
+
+ public String displayHierarchy() {
+ StringSerializer serializer = new StringSerializer();
+ displayHierarchy(this, 0, serializer);
+ return serializer.toString();
+ }
+
+ public void displayHierarchy(Component component, int indent, StringSerializer serializer) {
+ component.serializeToString(indent, serializer);
+ for (Operation c : component.mList) {
+ if (c instanceof ComponentModifiers) {
+ ((ComponentModifiers) c).serializeToString(indent + 1, serializer);
+ }
+ if (c instanceof Component) {
+ displayHierarchy((Component) c, indent + 1, serializer);
+ }
+ }
+ }
+
+ public static class Companion implements DocumentedCompanionOperation {
+ @Override
+ public String name() {
+ return "RootLayout";
+ }
+
+ @Override
+ public int id() {
+ return Operations.LAYOUT_ROOT;
+ }
+
+ public void apply(WireBuffer buffer) {
+ buffer.start(Operations.LAYOUT_ROOT);
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ operations.add(new RootLayoutComponent(
+ -1, 0, 0, 0, 0, null, -1));
+ }
+
+ @Override
+ public void documentation(DocumentationBuilder doc) {
+ doc.operation("Layout Operations", id(), name())
+ .description("Root element for a document. Other components / layout managers "
+ + "are children in the component tree starting from this Root component.");
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java
new file mode 100644
index 0000000..7c6bef4
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java
@@ -0,0 +1,311 @@
+/*
+ * 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.layout.animation;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.DecoratorComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
+import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation;
+import com.android.internal.widget.remotecompose.core.operations.utilities.easing.GeneralEasing;
+
+/**
+ * Basic interpolation manager between two ComponentMeasures
+ *
+ * Handles position, size and visibility
+ */
+public class AnimateMeasure {
+ long mStartTime = System.currentTimeMillis();
+ Component mComponent;
+ ComponentMeasure mOriginal;
+ ComponentMeasure mTarget;
+ int mDuration;
+ int mDurationVisibilityChange = mDuration;
+ AnimationSpec.ANIMATION mEnterAnimation = AnimationSpec.ANIMATION.FADE_IN;
+ AnimationSpec.ANIMATION mExitAnimation = AnimationSpec.ANIMATION.FADE_OUT;
+ int mMotionEasingType = GeneralEasing.CUBIC_STANDARD;
+ int mVisibilityEasingType = GeneralEasing.CUBIC_ACCELERATE;
+
+ float mP = 0f;
+ float mVp = 0f;
+ FloatAnimation mMotionEasing = new FloatAnimation(mMotionEasingType,
+ mDuration / 1000f, null, 0f, Float.NaN);
+ FloatAnimation mVisibilityEasing = new FloatAnimation(mVisibilityEasingType,
+ mDurationVisibilityChange / 1000f,
+ null, 0f, Float.NaN);
+ ParticleAnimation mParticleAnimation;
+
+ public AnimateMeasure(long startTime, Component component, ComponentMeasure original,
+ ComponentMeasure target, int duration, int durationVisibilityChange,
+ AnimationSpec.ANIMATION enterAnimation,
+ AnimationSpec.ANIMATION exitAnimation,
+ int motionEasingType, int visibilityEasingType) {
+ this.mStartTime = startTime;
+ this.mComponent = component;
+ this.mOriginal = original;
+ this.mTarget = target;
+ this.mDuration = duration;
+ this.mDurationVisibilityChange = durationVisibilityChange;
+ this.mEnterAnimation = enterAnimation;
+ this.mExitAnimation = exitAnimation;
+
+ mMotionEasing.setTargetValue(1f);
+ mVisibilityEasing.setTargetValue(1f);
+ component.mVisibility = target.getVisibility();
+ }
+
+ public void update(long currentTime) {
+ long elapsed = currentTime - mStartTime;
+ mP = Math.min(elapsed / (float) mDuration, 1f);
+ //mP = motionEasing.get(mP);
+ mVp = Math.min(elapsed / (float) mDurationVisibilityChange, 1f);
+ mVp = mVisibilityEasing.get(mVp);
+ }
+
+ public PaintBundle paint = new PaintBundle();
+
+ public void apply(PaintContext context) {
+ update(context.getContext().currentTime);
+
+ mComponent.setX(getX());
+ mComponent.setY(getY());
+ mComponent.setWidth(getWidth());
+ mComponent.setHeight(getHeight());
+
+ float w = mComponent.getWidth();
+ float h = mComponent.getHeight();
+ for (Operation op : mComponent.mList) {
+ if (op instanceof PaddingModifierOperation) {
+ PaddingModifierOperation pop = (PaddingModifierOperation) op;
+ w -= pop.getLeft() + pop.getRight();
+ h -= pop.getTop() + pop.getBottom();
+ }
+ if (op instanceof DecoratorComponent) {
+ ((DecoratorComponent) op).layout(context.getContext(), w, h);
+ }
+ }
+
+ mComponent.mVisibility = mTarget.getVisibility();
+ if (mOriginal.getVisibility() != mTarget.getVisibility()) {
+ if (mTarget.getVisibility() == Component.Visibility.GONE) {
+ switch (mExitAnimation) {
+ case PARTICLE:
+ // particleAnimation(context, component, original, target, vp)
+ if (mParticleAnimation == null) {
+ mParticleAnimation = new ParticleAnimation();
+ }
+ mParticleAnimation.animate(context, mComponent, mOriginal, mTarget, mVp);
+ break;
+ case FADE_OUT:
+ context.save();
+ context.savePaint();
+ paint.reset();
+ paint.setColor(0f, 0f, 0f, 1f - mVp);
+ context.applyPaint(paint);
+ context.saveLayer(mComponent.getX(), mComponent.getY(),
+ mComponent.getWidth(), mComponent.getHeight());
+ mComponent.paintingComponent(context);
+ context.restore();
+ context.restorePaint();
+ context.restore();
+ break;
+ case SLIDE_LEFT:
+ context.save();
+ context.translate(-mVp * mComponent.getParent().getWidth(), 0f);
+ context.saveLayer(mComponent.getX(), mComponent.getY(),
+ mComponent.getWidth(), mComponent.getHeight());
+ mComponent.paintingComponent(context);
+ context.restore();
+ context.restore();
+ break;
+ case SLIDE_RIGHT:
+ context.save();
+ context.savePaint();
+ paint.reset();
+ paint.setColor(0f, 0f, 0f, 1f);
+ context.applyPaint(paint);
+ context.translate(mVp * mComponent.getParent().getWidth(), 0f);
+ context.saveLayer(mComponent.getX(), mComponent.getY(),
+ mComponent.getWidth(), mComponent.getHeight());
+ mComponent.paintingComponent(context);
+ context.restore();
+ context.restorePaint();
+ context.restore();
+ break;
+ case SLIDE_TOP:
+ context.save();
+ context.translate(0f,
+ -mVp * mComponent.getParent().getHeight());
+ context.saveLayer(mComponent.getX(), mComponent.getY(),
+ mComponent.getWidth(), mComponent.getHeight());
+ mComponent.paintingComponent(context);
+ context.restore();
+ context.restore();
+ break;
+ case SLIDE_BOTTOM:
+ context.save();
+ context.translate(0f,
+ mVp * mComponent.getParent().getHeight());
+ context.saveLayer(mComponent.getX(), mComponent.getY(),
+ mComponent.getWidth(), mComponent.getHeight());
+ mComponent.paintingComponent(context);
+ context.restore();
+ context.restore();
+ break;
+ default:
+ // particleAnimation(context, component, original, target, vp)
+ if (mParticleAnimation == null) {
+ mParticleAnimation = new ParticleAnimation();
+ }
+ mParticleAnimation.animate(context, mComponent, mOriginal, mTarget, mVp);
+ break;
+ }
+ } else if (mOriginal.getVisibility() == Component.Visibility.GONE
+ && mTarget.getVisibility() == Component.Visibility.VISIBLE) {
+ switch (mEnterAnimation) {
+ case ROTATE:
+ float px = mTarget.getX() + mTarget.getW() / 2f;
+ float py = mTarget.getY() + mTarget.getH() / 2f;
+
+ context.save();
+ context.savePaint();
+ context.matrixRotate(mVp * 360f, px, py);
+ context.matrixScale(1f * mVp, 1f * mVp, px, py);
+ paint.reset();
+ paint.setColor(0f, 0f, 0f, mVp);
+ context.applyPaint(paint);
+ context.saveLayer(mComponent.getX(), mComponent.getY(),
+ mComponent.getWidth(), mComponent.getHeight());
+ mComponent.paintingComponent(context);
+ context.restore();
+ context.restorePaint();
+ context.restore();
+ break;
+ case FADE_IN:
+ context.save();
+ context.savePaint();
+ paint.reset();
+ paint.setColor(0f, 0f, 0f, mVp);
+ context.applyPaint(paint);
+ context.saveLayer(mComponent.getX(), mComponent.getY(),
+ mComponent.getWidth(), mComponent.getHeight());
+ mComponent.paintingComponent(context);
+ context.restore();
+ context.restorePaint();
+ context.restore();
+ break;
+ case SLIDE_LEFT:
+ context.save();
+ context.translate(
+ (1f - mVp) * mComponent.getParent().getWidth(), 0f);
+ context.saveLayer(mComponent.getX(), mComponent.getY(),
+ mComponent.getWidth(), mComponent.getHeight());
+ mComponent.paintingComponent(context);
+ context.restore();
+ context.restore();
+ break;
+ case SLIDE_RIGHT:
+ context.save();
+ context.translate(
+ -(1f - mVp) * mComponent.getParent().getWidth(), 0f);
+ context.saveLayer(mComponent.getX(), mComponent.getY(),
+ mComponent.getWidth(), mComponent.getHeight());
+ mComponent.paintingComponent(context);
+ context.restore();
+ context.restore();
+ break;
+ case SLIDE_TOP:
+ context.save();
+ context.translate(0f,
+ (1f - mVp) * mComponent.getParent().getHeight());
+ context.saveLayer(mComponent.getX(), mComponent.getY(),
+ mComponent.getWidth(), mComponent.getHeight());
+ mComponent.paintingComponent(context);
+ context.restore();
+ context.restore();
+ break;
+ case SLIDE_BOTTOM:
+ context.save();
+ context.translate(0f,
+ -(1f - mVp) * mComponent.getParent().getHeight());
+ context.saveLayer(mComponent.getX(), mComponent.getY(),
+ mComponent.getWidth(), mComponent.getHeight());
+ mComponent.paintingComponent(context);
+ context.restore();
+ context.restore();
+ break;
+ default:
+ break;
+ }
+ } else {
+ mComponent.paintingComponent(context);
+ }
+ } else {
+ mComponent.paintingComponent(context);
+ }
+
+ if (mP >= 1f && mVp >= 1f) {
+ mComponent.mAnimateMeasure = null;
+ mComponent.mVisibility = mTarget.getVisibility();
+ }
+ }
+
+ public boolean isDone() {
+ return mP >= 1f && mVp >= 1f;
+ }
+
+ public float getX() {
+ return mOriginal.getX() * (1 - mP) + mTarget.getX() * mP;
+ }
+
+ public float getY() {
+ return mOriginal.getY() * (1 - mP) + mTarget.getY() * mP;
+ }
+
+ public float getWidth() {
+ return mOriginal.getW() * (1 - mP) + mTarget.getW() * mP;
+ }
+
+ public float getHeight() {
+ return mOriginal.getH() * (1 - mP) + mTarget.getH() * mP;
+ }
+
+ public float getVisibility() {
+ if (mOriginal.getVisibility() == mTarget.getVisibility()) {
+ return 1f;
+ } else if (mTarget.getVisibility() == Component.Visibility.VISIBLE) {
+ return mVp;
+ } else {
+ return 1 - mVp;
+ }
+ }
+
+ public void updateTarget(ComponentMeasure measure, long currentTime) {
+ mOriginal.setX(getX());
+ mOriginal.setY(getY());
+ mOriginal.setW(getWidth());
+ mOriginal.setH(getHeight());
+ mTarget.setX(measure.getX());
+ mTarget.setY(measure.getY());
+ mTarget.setW(measure.getW());
+ mTarget.setH(measure.getH());
+ mTarget.setVisibility(measure.getVisibility());
+ mStartTime = currentTime;
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java
new file mode 100644
index 0000000..386d365
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java
@@ -0,0 +1,186 @@
+/*
+ * 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.layout.animation;
+
+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 com.android.internal.widget.remotecompose.core.operations.utilities.easing.GeneralEasing;
+
+import java.util.List;
+
+/**
+ * Basic component animation spec
+ */
+public class AnimationSpec implements Operation {
+
+ public static final AnimationSpec.Companion COMPANION = new AnimationSpec.Companion();
+
+ int mAnimationId = -1;
+ int mMotionDuration = 300;
+ int mMotionEasingType = GeneralEasing.CUBIC_STANDARD;
+ int mVisibilityDuration = 300;
+ int mVisibilityEasingType = GeneralEasing.CUBIC_STANDARD;
+ ANIMATION mEnterAnimation = ANIMATION.FADE_IN;
+ ANIMATION mExitAnimation = ANIMATION.FADE_OUT;
+
+ public AnimationSpec(int animationId, int motionDuration, int motionEasingType,
+ int visibilityDuration, int visibilityEasingType,
+ ANIMATION enterAnimation, ANIMATION exitAnimation) {
+ this.mAnimationId = animationId;
+ this.mMotionDuration = motionDuration;
+ this.mMotionEasingType = motionEasingType;
+ this.mVisibilityDuration = visibilityDuration;
+ this.mVisibilityEasingType = visibilityEasingType;
+ this.mEnterAnimation = enterAnimation;
+ this.mExitAnimation = exitAnimation;
+ }
+
+ public AnimationSpec() {
+ this(-1, 300, GeneralEasing.CUBIC_STANDARD,
+ 300, GeneralEasing.CUBIC_STANDARD,
+ ANIMATION.FADE_IN, ANIMATION.FADE_OUT);
+ }
+
+ public int getAnimationId() {
+ return mAnimationId;
+ }
+
+ public int getMotionDuration() {
+ return mMotionDuration;
+ }
+
+ public int getMotionEasingType() {
+ return mMotionEasingType;
+ }
+
+ public int getVisibilityDuration() {
+ return mVisibilityDuration;
+ }
+
+ public int getVisibilityEasingType() {
+ return mVisibilityEasingType;
+ }
+
+ public ANIMATION getEnterAnimation() {
+ return mEnterAnimation;
+ }
+
+ public ANIMATION getExitAnimation() {
+ return mExitAnimation;
+ }
+
+ @Override
+ public String toString() {
+ return "ANIMATION_SPEC (" + mMotionDuration + " ms)";
+ }
+
+ public enum ANIMATION {
+ FADE_IN,
+ FADE_OUT,
+ SLIDE_LEFT,
+ SLIDE_RIGHT,
+ SLIDE_TOP,
+ SLIDE_BOTTOM,
+ ROTATE,
+ PARTICLE
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ Companion.apply(buffer, mAnimationId, mMotionDuration, mMotionEasingType,
+ mVisibilityDuration, mVisibilityEasingType, mEnterAnimation, mExitAnimation);
+ }
+
+ @Override
+ public void apply(RemoteContext context) {
+ // nothing here
+ }
+
+ @Override
+ public String deepToString(String indent) {
+ return (indent != null ? indent : "") + toString();
+ }
+
+ public static class Companion implements CompanionOperation {
+ @Override
+ public String name() {
+ return "AnimationSpec";
+ }
+
+ @Override
+ public int id() {
+ return Operations.ANIMATION_SPEC;
+ }
+
+ public static int animationToInt(ANIMATION animation) {
+ return animation.ordinal();
+ }
+
+ public static ANIMATION intToAnimation(int value) {
+ switch (value) {
+ case 0:
+ return ANIMATION.FADE_IN;
+ case 1:
+ return ANIMATION.FADE_OUT;
+ case 2:
+ return ANIMATION.SLIDE_LEFT;
+ case 3:
+ return ANIMATION.SLIDE_RIGHT;
+ case 4:
+ return ANIMATION.SLIDE_TOP;
+ case 5:
+ return ANIMATION.SLIDE_BOTTOM;
+ case 6:
+ return ANIMATION.ROTATE;
+ case 7:
+ return ANIMATION.PARTICLE;
+ default:
+ return ANIMATION.FADE_IN;
+ }
+ }
+
+ public static void apply(WireBuffer buffer, int animationId, int motionDuration,
+ int motionEasingType, int visibilityDuration,
+ int visibilityEasingType, ANIMATION enterAnimation,
+ ANIMATION exitAnimation) {
+ buffer.start(Operations.ANIMATION_SPEC);
+ buffer.writeInt(animationId);
+ buffer.writeInt(motionDuration);
+ buffer.writeInt(motionEasingType);
+ buffer.writeInt(visibilityDuration);
+ buffer.writeInt(visibilityEasingType);
+ buffer.writeInt(animationToInt(enterAnimation));
+ buffer.writeInt(animationToInt(exitAnimation));
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int animationId = buffer.readInt();
+ int motionDuration = buffer.readInt();
+ int motionEasingType = buffer.readInt();
+ int visibilityDuration = buffer.readInt();
+ int visibilityEasingType = buffer.readInt();
+ ANIMATION enterAnimation = intToAnimation(buffer.readInt());
+ ANIMATION exitAnimation = intToAnimation(buffer.readInt());
+ AnimationSpec op = new AnimationSpec(animationId, motionDuration, motionEasingType,
+ visibilityDuration, visibilityEasingType, enterAnimation, exitAnimation);
+ operations.add(op);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/Particle.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/Particle.java
new file mode 100644
index 0000000..4562692
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/Particle.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout.animation;
+
+public class Particle {
+ public final float x;
+ public final float y;
+ public float radius;
+ public float r;
+ public float g;
+ public float b;
+
+ public Particle(float x, float y, float radius, float r, float g, float b) {
+ this.x = x;
+ this.y = y;
+ this.radius = radius;
+ this.r = r;
+ this.g = g;
+ this.b = b;
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java
new file mode 100644
index 0000000..5c5d056
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java
@@ -0,0 +1,64 @@
+/*
+ * 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.layout.animation;
+
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
+import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class ParticleAnimation {
+ HashMap<Integer, ArrayList<Particle>> mAllParticles = new HashMap<>();
+
+ PaintBundle mPaint = new PaintBundle();
+ public void animate(PaintContext context, Component component,
+ ComponentMeasure start, ComponentMeasure end,
+ float progress) {
+ ArrayList<Particle> particles = mAllParticles.get(component.getComponentId());
+ if (particles == null) {
+ particles = new ArrayList<Particle>();
+ for (int i = 0; i < 20; i++) {
+ float x = (float) Math.random();
+ float y = (float) Math.random();
+ float radius = (float) Math.random();
+ float r = 250f;
+ float g = 250f;
+ float b = 250f;
+ particles.add(new Particle(x, y, radius, r, g, b));
+ }
+ mAllParticles.put(component.getComponentId(), particles);
+ }
+ context.save();
+ context.savePaint();
+ for (int i = 0; i < particles.size(); i++) {
+ Particle particle = particles.get(i);
+ mPaint.reset();
+ mPaint.setColor(particle.r, particle.g, particle.b,
+ 200 * (1 - progress));
+ context.applyPaint(mPaint);
+ float dx = start.getX() + component.getWidth() * particle.x;
+ float dy = start.getY() + component.getHeight() * particle.y
+ + progress * 0.01f * component.getHeight();
+ float dr = (component.getHeight() + 60) * 0.15f * particle.radius + (30 * progress);
+ context.drawCircle(dx, dy, dr);
+ }
+ context.restorePaint();
+ context.restore();
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java
new file mode 100644
index 0000000..fea8dd2
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout.managers;
+
+import static com.android.internal.widget.remotecompose.core.documentation.Operation.INT;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedCompanionOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.ComponentStartOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size;
+
+import java.util.List;
+
+/**
+ * Simple Box layout implementation
+ */
+public class BoxLayout extends LayoutManager implements ComponentStartOperation {
+
+ public static final int START = 1;
+ public static final int CENTER = 2;
+ public static final int END = 3;
+ public static final int TOP = 4;
+ public static final int BOTTOM = 5;
+
+ public static final BoxLayout.Companion COMPANION = new BoxLayout.Companion();
+
+ int mHorizontalPositioning;
+ int mVerticalPositioning;
+
+ public BoxLayout(Component parent, int componentId, int animationId,
+ float x, float y, float width, float height,
+ int horizontalPositioning, int verticalPositioning) {
+ super(parent, componentId, animationId, x, y, width, height);
+ mHorizontalPositioning = horizontalPositioning;
+ mVerticalPositioning = verticalPositioning;
+ }
+
+ public BoxLayout(Component parent, int componentId, int animationId,
+ int horizontalPositioning, int verticalPositioning) {
+ this(parent, componentId, animationId, 0, 0, 0, 0,
+ horizontalPositioning, verticalPositioning);
+ }
+
+ @Override
+ public String toString() {
+ return "BOX [" + mComponentId + ":" + mAnimationId + "] (" + mX + ", "
+ + mY + " - " + mWidth + " x " + mHeight + ") " + mVisibility;
+ }
+
+ protected String getSerializedName() {
+ return "BOX";
+ }
+
+ @Override
+ public void computeWrapSize(PaintContext context, float maxWidth, float maxHeight,
+ MeasurePass measure, Size size) {
+ for (Component c : mChildrenComponents) {
+ c.measure(context, 0f, maxWidth, 0f, maxHeight, measure);
+ ComponentMeasure m = measure.get(c);
+ size.setWidth(Math.max(size.getWidth(), m.getW()));
+ size.setHeight(Math.max(size.getHeight(), m.getH()));
+ }
+ // add padding
+ size.setWidth(Math.max(size.getWidth(), computeModifierDefinedWidth()));
+ size.setHeight(Math.max(size.getHeight(), computeModifierDefinedHeight()));
+ }
+
+ @Override
+ public void computeSize(PaintContext context, float minWidth, float maxWidth,
+ float minHeight, float maxHeight, MeasurePass measure) {
+ for (Component child : mChildrenComponents) {
+ child.measure(context, minWidth, maxWidth, minHeight, maxHeight, measure);
+ }
+ }
+
+ @Override
+ public void internalLayoutMeasure(PaintContext context,
+ MeasurePass measure) {
+ ComponentMeasure selfMeasure = measure.get(this);
+ float selfWidth = selfMeasure.getW() - mPaddingLeft - mPaddingRight;
+ float selfHeight = selfMeasure.getH() - mPaddingTop - mPaddingBottom;
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure m = measure.get(child);
+ float tx = 0f;
+ float ty = 0f;
+ switch (mVerticalPositioning) {
+ case TOP:
+ ty = 0f;
+ break;
+ case CENTER:
+ ty = (selfHeight - m.getH()) / 2f;
+ break;
+ case BOTTOM:
+ ty = selfHeight - m.getH();
+ break;
+ }
+ switch (mHorizontalPositioning) {
+ case START:
+ tx = 0f;
+ break;
+ case CENTER:
+ tx = (selfWidth - m.getW()) / 2f;
+ break;
+ case END:
+ tx = selfWidth - m.getW();
+ break;
+ }
+ m.setX(tx);
+ m.setY(ty);
+ m.setVisibility(child.mVisibility);
+ }
+ }
+
+ public static class Companion implements DocumentedCompanionOperation {
+ @Override
+ public String name() {
+ return "BoxLayout";
+ }
+
+ @Override
+ public int id() {
+ return Operations.LAYOUT_BOX;
+ }
+
+ public void apply(WireBuffer buffer, int componentId, int animationId,
+ int horizontalPositioning, int verticalPositioning) {
+ buffer.start(Operations.LAYOUT_BOX);
+ buffer.writeInt(componentId);
+ buffer.writeInt(animationId);
+ buffer.writeInt(horizontalPositioning);
+ buffer.writeInt(verticalPositioning);
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int componentId = buffer.readInt();
+ int animationId = buffer.readInt();
+ int horizontalPositioning = buffer.readInt();
+ int verticalPositioning = buffer.readInt();
+ operations.add(new BoxLayout(null, componentId, animationId,
+ horizontalPositioning, verticalPositioning));
+ }
+
+ @Override
+ public void documentation(DocumentationBuilder doc) {
+ doc.operation("Layout Operations", id(), name())
+ .description("Box layout implementation.\n\n"
+ + "Child components are laid out independently from one another,\n"
+ + " and painted in their hierarchy order (first children drawn"
+ + "before the latter). Horizontal and Vertical positioning"
+ + "are supported.")
+ .examplesDimension(150, 100)
+ .exampleImage("Top", "layout-BoxLayout-start-top.png")
+ .exampleImage("Center", "layout-BoxLayout-center-center.png")
+ .exampleImage("Bottom", "layout-BoxLayout-end-bottom.png")
+ .field(INT, "COMPONENT_ID", "unique id for this component")
+ .field(INT, "ANIMATION_ID", "id used to match components,"
+ + " for animation purposes")
+ .field(INT, "HORIZONTAL_POSITIONING", "horizontal positioning value")
+ .possibleValues("START", BoxLayout.START)
+ .possibleValues("CENTER", BoxLayout.CENTER)
+ .possibleValues("END", BoxLayout.END)
+ .field(INT, "VERTICAL_POSITIONING", "vertical positioning value")
+ .possibleValues("TOP", BoxLayout.TOP)
+ .possibleValues("CENTER", BoxLayout.CENTER)
+ .possibleValues("BOTTOM", BoxLayout.BOTTOM);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
new file mode 100644
index 0000000..a1a2de5
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
@@ -0,0 +1,291 @@
+/*
+ * 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.layout.managers;
+
+import static com.android.internal.widget.remotecompose.core.documentation.Operation.FLOAT;
+import static com.android.internal.widget.remotecompose.core.documentation.Operation.INT;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedCompanionOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.ComponentStartOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size;
+import com.android.internal.widget.remotecompose.core.operations.layout.utils.DebugLog;
+
+import java.util.List;
+
+/**
+ * Simple Column layout implementation
+ * - also supports weight and horizontal/vertical positioning
+ */
+public class ColumnLayout extends LayoutManager implements ComponentStartOperation {
+ public static final int START = 1;
+ public static final int CENTER = 2;
+ public static final int END = 3;
+ public static final int TOP = 4;
+ public static final int BOTTOM = 5;
+ public static final int SPACE_BETWEEN = 6;
+ public static final int SPACE_EVENLY = 7;
+ public static final int SPACE_AROUND = 8;
+
+ public static final ColumnLayout.Companion COMPANION = new ColumnLayout.Companion();
+
+ int mHorizontalPositioning;
+ int mVerticalPositioning;
+ float mSpacedBy = 0f;
+
+ public ColumnLayout(Component parent, int componentId, int animationId,
+ float x, float y, float width, float height,
+ int horizontalPositioning, int verticalPositioning, float spacedBy) {
+ super(parent, componentId, animationId, x, y, width, height);
+ mHorizontalPositioning = horizontalPositioning;
+ mVerticalPositioning = verticalPositioning;
+ mSpacedBy = spacedBy;
+ }
+
+ public ColumnLayout(Component parent, int componentId, int animationId,
+ int horizontalPositioning, int verticalPositioning, float spacedBy) {
+ this(parent, componentId, animationId, 0, 0, 0, 0,
+ horizontalPositioning, verticalPositioning, spacedBy);
+ }
+
+ @Override
+ public String toString() {
+ return "COLUMN [" + mComponentId + ":" + mAnimationId + "] (" + mX + ", "
+ + mY + " - " + mWidth + " x " + mHeight + ") " + mVisibility;
+ }
+
+ protected String getSerializedName() {
+ return "COLUMN";
+ }
+
+ @Override
+ public void computeWrapSize(PaintContext context, float maxWidth, float maxHeight,
+ MeasurePass measure, Size size) {
+ DebugLog.s(() -> "COMPUTE WRAP SIZE in " + this + " (" + mComponentId + ")");
+ for (Component c : mChildrenComponents) {
+ c.measure(context, 0f, maxWidth,
+ 0f, maxHeight, measure);
+ ComponentMeasure m = measure.get(c);
+ size.setWidth(Math.max(size.getWidth(), m.getW()));
+ size.setHeight(size.getHeight() + m.getH());
+ }
+ if (!mChildrenComponents.isEmpty()) {
+ size.setHeight(size.getHeight()
+ + (mSpacedBy * (mChildrenComponents.size() - 1)));
+ }
+ DebugLog.e();
+ }
+
+ @Override
+ public void computeSize(PaintContext context, float minWidth, float maxWidth,
+ float minHeight, float maxHeight, MeasurePass measure) {
+ DebugLog.s(() -> "COMPUTE SIZE in " + this + " (" + mComponentId + ")");
+ float mh = maxHeight;
+ for (Component child : mChildrenComponents) {
+ child.measure(context, minWidth, maxWidth, minHeight, mh, measure);
+ ComponentMeasure m = measure.get(child);
+ mh -= m.getH();
+ }
+ DebugLog.e();
+ }
+
+ @Override
+ public void internalLayoutMeasure(PaintContext context,
+ MeasurePass measure) {
+ ComponentMeasure selfMeasure = measure.get(this);
+ DebugLog.s(() -> "INTERNAL LAYOUT " + this + " (" + mComponentId + ") children: "
+ + mChildrenComponents.size() + " size (" + selfMeasure.getW()
+ + " x " + selfMeasure.getH() + ")");
+ if (mChildrenComponents.isEmpty()) {
+ DebugLog.e();
+ return;
+ }
+ float selfWidth = selfMeasure.getW() - mPaddingLeft - mPaddingRight;
+ float selfHeight = selfMeasure.getH() - mPaddingTop - mPaddingBottom;
+ float childrenWidth = 0f;
+ float childrenHeight = 0f;
+
+ boolean hasWeights = false;
+ float totalWeights = 0f;
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ if (child instanceof LayoutComponent
+ && ((LayoutComponent) child).getHeightModifier().hasWeight()) {
+ hasWeights = true;
+ totalWeights += ((LayoutComponent) child).getHeightModifier().getValue();
+ } else {
+ childrenHeight += childMeasure.getH();
+ }
+ }
+ if (hasWeights) {
+ float availableSpace = selfHeight - childrenHeight;
+ for (Component child : mChildrenComponents) {
+ if (child instanceof LayoutComponent
+ && ((LayoutComponent) child).getHeightModifier().hasWeight()) {
+ ComponentMeasure childMeasure = measure.get(child);
+ float weight = ((LayoutComponent) child).getHeightModifier().getValue();
+ childMeasure.setH((weight * availableSpace) / totalWeights);
+ child.measure(context, childMeasure.getW(),
+ childMeasure.getW(), childMeasure.getH(), childMeasure.getH(), measure);
+ }
+ }
+ }
+
+ childrenHeight = 0f;
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ childrenWidth = Math.max(childrenWidth, childMeasure.getW());
+ childrenHeight += childMeasure.getH();
+ }
+ childrenHeight += mSpacedBy * (mChildrenComponents.size() - 1);
+
+ float tx = 0f;
+ float ty = 0f;
+
+ float verticalGap = 0f;
+ float total = 0f;
+ switch (mVerticalPositioning) {
+ case TOP:
+ ty = 0f;
+ break;
+ case CENTER:
+ ty = (selfHeight - childrenHeight) / 2f;
+ break;
+ case BOTTOM:
+ ty = selfHeight - childrenHeight;
+ break;
+ case SPACE_BETWEEN:
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ total += childMeasure.getH();
+ }
+ verticalGap = (selfHeight - total) / (mChildrenComponents.size() - 1);
+ break;
+ case SPACE_EVENLY:
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ total += childMeasure.getH();
+ }
+ verticalGap = (selfHeight - total) / (mChildrenComponents.size() + 1);
+ ty = verticalGap;
+ break;
+ case SPACE_AROUND:
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ total += childMeasure.getH();
+ }
+ verticalGap = (selfHeight - total) / (mChildrenComponents.size());
+ ty = verticalGap / 2f;
+ break;
+ }
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ switch (mHorizontalPositioning) {
+ case START:
+ tx = 0f;
+ break;
+ case CENTER:
+ tx = (selfWidth - childMeasure.getW()) / 2f;
+ break;
+ case END:
+ tx = selfWidth - childMeasure.getW();
+ break;
+ }
+ childMeasure.setX(tx);
+ childMeasure.setY(ty);
+ childMeasure.setVisibility(child.mVisibility);
+ ty += childMeasure.getH();
+ if (mVerticalPositioning == SPACE_BETWEEN
+ || mVerticalPositioning == SPACE_AROUND
+ || mVerticalPositioning == SPACE_EVENLY) {
+ ty += verticalGap;
+ }
+ ty += mSpacedBy;
+ }
+ DebugLog.e();
+ }
+
+ public static class Companion implements DocumentedCompanionOperation {
+ @Override
+ public String name() {
+ return "ColumnLayout";
+ }
+
+ @Override
+ public int id() {
+ return Operations.LAYOUT_COLUMN;
+ }
+
+ public void apply(WireBuffer buffer, int componentId, int animationId,
+ int horizontalPositioning, int verticalPositioning, float spacedBy) {
+ buffer.start(Operations.LAYOUT_COLUMN);
+ buffer.writeInt(componentId);
+ buffer.writeInt(animationId);
+ buffer.writeInt(horizontalPositioning);
+ buffer.writeInt(verticalPositioning);
+ buffer.writeFloat(spacedBy);
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int componentId = buffer.readInt();
+ int animationId = buffer.readInt();
+ int horizontalPositioning = buffer.readInt();
+ int verticalPositioning = buffer.readInt();
+ float spacedBy = buffer.readFloat();
+ operations.add(new ColumnLayout(null, componentId, animationId,
+ horizontalPositioning, verticalPositioning, spacedBy));
+ }
+
+ @Override
+ public void documentation(DocumentationBuilder doc) {
+ doc.operation("Layout Operations", id(), name())
+ .description("Column layout implementation, positioning components one"
+ + " after the other vertically.\n\n"
+ + "It supports weight and horizontal/vertical positioning.")
+ .examplesDimension(100, 400)
+ .exampleImage("Top", "layout-ColumnLayout-start-top.png")
+ .exampleImage("Center", "layout-ColumnLayout-start-center.png")
+ .exampleImage("Bottom", "layout-ColumnLayout-start-bottom.png")
+ .exampleImage("SpaceEvenly", "layout-ColumnLayout-start-space-evenly.png")
+ .exampleImage("SpaceAround", "layout-ColumnLayout-start-space-around.png")
+ .exampleImage("SpaceBetween", "layout-ColumnLayout-start-space-between.png")
+ .field(INT, "COMPONENT_ID", "unique id for this component")
+ .field(INT, "ANIMATION_ID", "id used to match components,"
+ + " for animation purposes")
+ .field(INT, "HORIZONTAL_POSITIONING", "horizontal positioning value")
+ .possibleValues("START", ColumnLayout.START)
+ .possibleValues("CENTER", ColumnLayout.CENTER)
+ .possibleValues("END", ColumnLayout.END)
+ .field(INT, "VERTICAL_POSITIONING", "vertical positioning value")
+ .possibleValues("TOP", ColumnLayout.TOP)
+ .possibleValues("CENTER", ColumnLayout.CENTER)
+ .possibleValues("BOTTOM", ColumnLayout.BOTTOM)
+ .possibleValues("SPACE_BETWEEN", ColumnLayout.SPACE_BETWEEN)
+ .possibleValues("SPACE_EVENLY", ColumnLayout.SPACE_EVENLY)
+ .possibleValues("SPACE_AROUND", ColumnLayout.SPACE_AROUND)
+ .field(FLOAT, "SPACED_BY", "Horizontal spacing between components");
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java
new file mode 100644
index 0000000..4890683
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.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.layout.managers;
+
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.Measurable;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size;
+
+/**
+ * Base class for layout managers -- resizable components.
+ */
+public abstract class LayoutManager extends LayoutComponent implements Measurable {
+
+ Size mCachedWrapSize = new Size(0f, 0f);
+
+ public LayoutManager(Component parent, int componentId, int animationId,
+ float x, float y, float width, float height) {
+ super(parent, componentId, animationId, x, y, width, height);
+ }
+
+ /**
+ * Implemented by subclasses to provide a layout/measure pass
+ */
+ public void internalLayoutMeasure(PaintContext context,
+ MeasurePass measure) {
+ // nothing here
+ }
+
+ /**
+ * Subclasses can implement this to provide wrap sizing
+ */
+ public void computeWrapSize(PaintContext context, float maxWidth, float maxHeight,
+ MeasurePass measure, Size size) {
+ // nothing here
+ }
+
+ /**
+ * Subclasses can implement this when not in wrap sizing
+ */
+ public void computeSize(PaintContext context, float minWidth, float maxWidth,
+ float minHeight, float maxHeight, MeasurePass measure) {
+ // nothing here
+ }
+
+ /**
+ * Base implementation of the measure resolution
+ */
+ @Override
+ public void measure(PaintContext context, float minWidth, float maxWidth,
+ float minHeight, float maxHeight, MeasurePass measure) {
+ boolean hasWrap = true;
+ float measuredWidth = Math.min(maxWidth,
+ computeModifierDefinedWidth() - mMarginLeft - mMarginRight);
+ float measuredHeight = Math.min(maxHeight,
+ computeModifierDefinedHeight() - mMarginTop - mMarginBottom);
+ float insetMaxWidth = maxWidth - mMarginLeft - mMarginRight;
+ float insetMaxHeight = maxHeight - mMarginTop - mMarginBottom;
+ if (mWidthModifier.isWrap() || mHeightModifier.isWrap()) {
+ mCachedWrapSize.setWidth(0f);
+ mCachedWrapSize.setHeight(0f);
+ computeWrapSize(context, maxWidth, maxHeight, measure, mCachedWrapSize);
+ measuredWidth = mCachedWrapSize.getWidth();
+ measuredHeight = mCachedWrapSize.getHeight();
+ } else {
+ hasWrap = false;
+ }
+ if (mWidthModifier.isFill()) {
+ measuredWidth = insetMaxWidth;
+ } else if (mWidthModifier.hasWeight()) {
+ measuredWidth = Math.max(measuredWidth, computeModifierDefinedWidth());
+ } else {
+ measuredWidth = Math.max(measuredWidth, minWidth);
+ measuredWidth = Math.min(measuredWidth, insetMaxWidth);
+ }
+ if (mHeightModifier.isFill()) {
+ measuredHeight = insetMaxHeight;
+ } else if (mHeightModifier.hasWeight()) {
+ measuredHeight = Math.max(measuredHeight, computeModifierDefinedHeight());
+ } else {
+ measuredHeight = Math.max(measuredHeight, minHeight);
+ measuredHeight = Math.min(measuredHeight, insetMaxHeight);
+ }
+ if (minWidth == maxWidth) {
+ measuredWidth = maxWidth;
+ }
+ if (minHeight == maxHeight) {
+ measuredHeight = maxHeight;
+ }
+ measuredWidth = Math.min(measuredWidth, insetMaxWidth);
+ measuredHeight = Math.min(measuredHeight, insetMaxHeight);
+ if (!hasWrap) {
+ computeSize(context, 0f, measuredWidth, 0f, measuredHeight, measure);
+ }
+ measuredWidth += mMarginLeft + mMarginRight;
+ measuredHeight += mMarginTop + mMarginBottom;
+
+ ComponentMeasure m = measure.get(this);
+ m.setW(measuredWidth);
+ m.setH(measuredHeight);
+
+ internalLayoutMeasure(context, measure);
+ }
+
+ /**
+ * basic layout of internal components
+ */
+ @Override
+ public void layout(RemoteContext context, MeasurePass measure) {
+ super.layout(context, measure);
+ ComponentMeasure self = measure.get(this);
+
+ mComponentModifiers.layout(context, self.getW(), self.getH());
+ for (Component c : mChildrenComponents) {
+ c.layout(context, measure);
+ }
+ this.mNeedsMeasure = false;
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
new file mode 100644
index 0000000..07e2ea1
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout.managers;
+
+import static com.android.internal.widget.remotecompose.core.documentation.Operation.FLOAT;
+import static com.android.internal.widget.remotecompose.core.documentation.Operation.INT;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentedCompanionOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.ComponentStartOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
+import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size;
+import com.android.internal.widget.remotecompose.core.operations.layout.utils.DebugLog;
+
+import java.util.List;
+
+/**
+ * Simple Row layout implementation
+ * - also supports weight and horizontal/vertical positioning
+ */
+public class RowLayout extends LayoutManager implements ComponentStartOperation {
+ public static final int START = 1;
+ public static final int CENTER = 2;
+ public static final int END = 3;
+ public static final int TOP = 4;
+ public static final int BOTTOM = 5;
+ public static final int SPACE_BETWEEN = 6;
+ public static final int SPACE_EVENLY = 7;
+ public static final int SPACE_AROUND = 8;
+
+ public static final RowLayout.Companion COMPANION = new RowLayout.Companion();
+
+ int mHorizontalPositioning;
+ int mVerticalPositioning;
+ float mSpacedBy = 0f;
+
+ public RowLayout(Component parent, int componentId, int animationId,
+ float x, float y, float width, float height,
+ int horizontalPositioning, int verticalPositioning, float spacedBy) {
+ super(parent, componentId, animationId, x, y, width, height);
+ mHorizontalPositioning = horizontalPositioning;
+ mVerticalPositioning = verticalPositioning;
+ mSpacedBy = spacedBy;
+ }
+
+ public RowLayout(Component parent, int componentId, int animationId,
+ int horizontalPositioning, int verticalPositioning, float spacedBy) {
+ this(parent, componentId, animationId, 0, 0, 0, 0,
+ horizontalPositioning, verticalPositioning, spacedBy);
+ }
+ @Override
+ public String toString() {
+ return "ROW [" + mComponentId + ":" + mAnimationId + "] (" + mX + ", "
+ + mY + " - " + mWidth + " x " + mHeight + ") " + mVisibility;
+ }
+
+ protected String getSerializedName() {
+ return "ROW";
+ }
+
+ @Override
+ public void computeWrapSize(PaintContext context, float maxWidth, float maxHeight,
+ MeasurePass measure, Size size) {
+ DebugLog.s(() -> "COMPUTE WRAP SIZE in " + this + " (" + mComponentId + ")");
+ for (Component c : mChildrenComponents) {
+ c.measure(context, 0f, maxWidth, 0f, maxHeight, measure);
+ ComponentMeasure m = measure.get(c);
+ size.setWidth(size.getWidth() + m.getW());
+ size.setHeight(Math.max(size.getHeight(), m.getH()));
+ }
+ if (!mChildrenComponents.isEmpty()) {
+ size.setWidth(size.getWidth()
+ + (mSpacedBy * (mChildrenComponents.size() - 1)));
+ }
+ DebugLog.e();
+ }
+
+ @Override
+ public void computeSize(PaintContext context, float minWidth, float maxWidth,
+ float minHeight, float maxHeight, MeasurePass measure) {
+ DebugLog.s(() -> "COMPUTE SIZE in " + this + " (" + mComponentId + ")");
+ float mw = maxWidth;
+ for (Component child : mChildrenComponents) {
+ child.measure(context, minWidth, mw, minHeight, maxHeight, measure);
+ ComponentMeasure m = measure.get(child);
+ mw -= m.getW();
+ }
+ DebugLog.e();
+ }
+
+ @Override
+ public void internalLayoutMeasure(PaintContext context,
+ MeasurePass measure) {
+ ComponentMeasure selfMeasure = measure.get(this);
+ DebugLog.s(() -> "INTERNAL LAYOUT " + this + " (" + mComponentId + ") children: "
+ + mChildrenComponents.size() + " size (" + selfMeasure.getW()
+ + " x " + selfMeasure.getH() + ")");
+ if (mChildrenComponents.isEmpty()) {
+ DebugLog.e();
+ return;
+ }
+ float selfWidth = selfMeasure.getW() - mPaddingLeft - mPaddingRight;
+ float selfHeight = selfMeasure.getH() - mPaddingTop - mPaddingBottom;
+ float childrenWidth = 0f;
+ float childrenHeight = 0f;
+
+ boolean hasWeights = false;
+ float totalWeights = 0f;
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ if (child instanceof LayoutComponent
+ && ((LayoutComponent) child).getWidthModifier().hasWeight()) {
+ hasWeights = true;
+ totalWeights += ((LayoutComponent) child).getWidthModifier().getValue();
+ } else {
+ childrenWidth += childMeasure.getW();
+ }
+ }
+
+ // TODO: need to move the weight measuring in the measure function,
+ // currently we'll measure unnecessarily
+ if (hasWeights) {
+ float availableSpace = selfWidth - childrenWidth;
+ for (Component child : mChildrenComponents) {
+ if (child instanceof LayoutComponent
+ && ((LayoutComponent) child).getWidthModifier().hasWeight()) {
+ ComponentMeasure childMeasure = measure.get(child);
+ float weight = ((LayoutComponent) child).getWidthModifier().getValue();
+ childMeasure.setW((weight * availableSpace) / totalWeights);
+ child.measure(context, childMeasure.getW(),
+ childMeasure.getW(), childMeasure.getH(), childMeasure.getH(), measure);
+ }
+ }
+ }
+
+ childrenWidth = 0f;
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ childrenWidth += childMeasure.getW();
+ childrenHeight = Math.max(childrenHeight, childMeasure.getH());
+ }
+ childrenWidth += mSpacedBy * (mChildrenComponents.size() - 1);
+
+ float tx = 0f;
+ float ty = 0f;
+
+ float horizontalGap = 0f;
+ float total = 0f;
+
+ switch (mHorizontalPositioning) {
+ case START:
+ tx = 0f;
+ break;
+ case END:
+ tx = selfWidth - childrenWidth;
+ break;
+ case CENTER:
+ tx = (selfWidth - childrenWidth) / 2f;
+ break;
+ case SPACE_BETWEEN:
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ total += childMeasure.getW();
+ }
+ horizontalGap = (selfWidth - total) / (mChildrenComponents.size() - 1);
+ break;
+ case SPACE_EVENLY:
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ total += childMeasure.getW();
+ }
+ horizontalGap = (selfWidth - total) / (mChildrenComponents.size() + 1);
+ tx = horizontalGap;
+ break;
+ case SPACE_AROUND:
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ total += childMeasure.getW();
+ }
+ horizontalGap = (selfWidth - total) / (mChildrenComponents.size());
+ tx = horizontalGap / 2f;
+ break;
+ }
+
+ for (Component child : mChildrenComponents) {
+ ComponentMeasure childMeasure = measure.get(child);
+ switch (mVerticalPositioning) {
+ case TOP:
+ ty = 0f;
+ break;
+ case CENTER:
+ ty = (selfHeight - childMeasure.getH()) / 2f;
+ break;
+ case BOTTOM:
+ ty = selfHeight - childMeasure.getH();
+ break;
+ }
+ childMeasure.setX(tx);
+ childMeasure.setY(ty);
+ childMeasure.setVisibility(child.mVisibility);
+ tx += childMeasure.getW();
+ if (mHorizontalPositioning == SPACE_BETWEEN
+ || mHorizontalPositioning == SPACE_AROUND
+ || mHorizontalPositioning == SPACE_EVENLY) {
+ tx += horizontalGap;
+ }
+ tx += mSpacedBy;
+ }
+ DebugLog.e();
+ }
+
+ public static class Companion implements DocumentedCompanionOperation {
+ @Override
+ public String name() {
+ return "RowLayout";
+ }
+
+ @Override
+ public int id() {
+ return Operations.LAYOUT_ROW;
+ }
+
+ public void apply(WireBuffer buffer, int componentId, int animationId,
+ int horizontalPositioning, int verticalPositioning, float spacedBy) {
+ buffer.start(Operations.LAYOUT_ROW);
+ buffer.writeInt(componentId);
+ buffer.writeInt(animationId);
+ buffer.writeInt(horizontalPositioning);
+ buffer.writeInt(verticalPositioning);
+ buffer.writeFloat(spacedBy);
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int componentId = buffer.readInt();
+ int animationId = buffer.readInt();
+ int horizontalPositioning = buffer.readInt();
+ int verticalPositioning = buffer.readInt();
+ float spacedBy = buffer.readFloat();
+ operations.add(new RowLayout(null, componentId, animationId,
+ horizontalPositioning, verticalPositioning, spacedBy));
+ }
+
+ @Override
+ public void documentation(DocumentationBuilder doc) {
+ doc.operation("Layout Operations", id(), name())
+ .description("Row layout implementation, positioning components one"
+ + " after the other horizontally.\n\n"
+ + "It supports weight and horizontal/vertical positioning.")
+ .examplesDimension(400, 100)
+ .exampleImage("Start", "layout-RowLayout-start-top.png")
+ .exampleImage("Center", "layout-RowLayout-center-top.png")
+ .exampleImage("End", "layout-RowLayout-end-top.png")
+ .exampleImage("SpaceEvenly", "layout-RowLayout-space-evenly-top.png")
+ .exampleImage("SpaceAround", "layout-RowLayout-space-around-top.png")
+ .exampleImage("SpaceBetween", "layout-RowLayout-space-between-top.png")
+ .field(INT, "COMPONENT_ID", "unique id for this component")
+ .field(INT, "ANIMATION_ID", "id used to match components,"
+ + " for animation purposes")
+ .field(INT, "HORIZONTAL_POSITIONING", "horizontal positioning value")
+ .possibleValues("START", RowLayout.START)
+ .possibleValues("CENTER", RowLayout.CENTER)
+ .possibleValues("END", RowLayout.END)
+ .possibleValues("SPACE_BETWEEN", RowLayout.SPACE_BETWEEN)
+ .possibleValues("SPACE_EVENLY", RowLayout.SPACE_EVENLY)
+ .possibleValues("SPACE_AROUND", RowLayout.SPACE_AROUND)
+ .field(INT, "VERTICAL_POSITIONING", "vertical positioning value")
+ .possibleValues("TOP", RowLayout.TOP)
+ .possibleValues("CENTER", RowLayout.CENTER)
+ .possibleValues("BOTTOM", RowLayout.BOTTOM)
+ .field(FLOAT, "SPACED_BY", "Horizontal spacing between components");
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java
new file mode 100644
index 0000000..8dc10d5
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java
@@ -0,0 +1,91 @@
+/*
+ * 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.layout.measure;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+
+/**
+ * Encapsulate the result of a measure pass for a component
+ */
+public class ComponentMeasure {
+ int mId = -1;
+ float mX;
+ float mY;
+ float mW;
+ float mH;
+ Component.Visibility mVisibility = Component.Visibility.VISIBLE;
+
+ public void setX(float value) {
+ mX = value;
+ }
+ public void setY(float value) {
+ mY = value;
+ }
+ public void setW(float value) {
+ mW = value;
+ }
+ public void setH(float value) {
+ mH = value;
+ }
+ public float getX() {
+ return mX;
+ }
+ public float getY() {
+ return mY;
+ }
+ public float getW() {
+ return mW;
+ }
+ public float getH() {
+ return mH;
+ }
+
+ public Component.Visibility getVisibility() {
+ return mVisibility;
+ }
+
+ public void setVisibility(Component.Visibility visibility) {
+ mVisibility = visibility;
+ }
+
+ public ComponentMeasure(int id, float x, float y, float w, float h,
+ Component.Visibility visibility) {
+ this.mId = id;
+ this.mX = x;
+ this.mY = y;
+ this.mW = w;
+ this.mH = h;
+ this.mVisibility = visibility;
+ }
+
+ public ComponentMeasure(int id, float x, float y, float w, float h) {
+ this(id, x, y, w, h, Component.Visibility.VISIBLE);
+ }
+
+ public ComponentMeasure(Component component) {
+ this(component.getComponentId(), component.getX(), component.getY(),
+ component.getWidth(), component.getHeight(),
+ component.mVisibility);
+ }
+
+ public void copyFrom(ComponentMeasure m) {
+ mX = m.mX;
+ mY = m.mY;
+ mW = m.mW;
+ mH = m.mH;
+ mVisibility = m.mVisibility;
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/Measurable.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/Measurable.java
new file mode 100644
index 0000000..d167d9b
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/Measurable.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget.remotecompose.core.operations.layout.measure;
+
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+
+/**
+ * Interface describing the measure/layout contract for components
+ */
+public interface Measurable {
+
+ /**
+ * Measure a component and store the result of the measure in the provided MeasurePass.
+ * This does not apply the measure to the component.
+ */
+ void measure(PaintContext context, float minWidth, float maxWidth,
+ float minHeight, float maxHeight, MeasurePass measure);
+
+ /**
+ * Apply a given measure to the component
+ */
+ void layout(RemoteContext context, MeasurePass measure);
+
+ /**
+ * Return true if the component needs to be remeasured
+ * @return true if need to remeasured, false otherwise
+ */
+ boolean needsMeasure();
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java
new file mode 100644
index 0000000..6801deb
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout.measure;
+
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+
+import java.util.HashMap;
+
+/**
+ * Represents the result of a measure pass on the entire hierarchy
+ * TODO: optimize to use a flat array vs the current hashmap
+ */
+public class MeasurePass {
+ HashMap<Integer, ComponentMeasure> mList = new HashMap<>();
+
+ public void clear() {
+ mList.clear();
+ }
+
+ public void add(ComponentMeasure measure) throws Exception {
+ if (measure.mId == -1) {
+ throw new Exception("Component has no id!");
+ }
+ mList.put(measure.mId, measure);
+ }
+
+ public boolean contains(int id) {
+ return mList.containsKey(id);
+ }
+
+ public ComponentMeasure get(Component c) {
+ if (!mList.containsKey(c.getComponentId())) {
+ ComponentMeasure measure = new ComponentMeasure(c.getComponentId(),
+ c.getX(), c.getY(), c.getWidth(), c.getHeight());
+ mList.put(c.getComponentId(), measure);
+ return measure;
+ }
+ return mList.get(c.getComponentId());
+ }
+
+ public ComponentMeasure get(int id) {
+ if (!mList.containsKey(id)) {
+ ComponentMeasure measure = new ComponentMeasure(id,
+ 0f, 0f, 0f, 0f, Component.Visibility.GONE);
+ mList.put(id, measure);
+ return measure;
+ }
+ return mList.get(id);
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/Size.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/Size.java
new file mode 100644
index 0000000..b11d8e8
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/Size.java
@@ -0,0 +1,44 @@
+/*
+ * 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.layout.measure;
+
+/**
+ * Basic data class representing a component size, used during layout computations.
+ */
+public class Size {
+ float mWidth;
+ float mHeight;
+ public Size(float width, float height) {
+ this.mWidth = width;
+ this.mHeight = height;
+ }
+
+ public void setWidth(float value) {
+ mWidth = value;
+ }
+
+ public void setHeight(float value) {
+ mHeight = value;
+ }
+
+ public float getWidth() {
+ return mWidth;
+ }
+
+ public float getHeight() {
+ return mHeight;
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java
new file mode 100644
index 0000000..6f48aee
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java
@@ -0,0 +1,146 @@
+/*
+ * 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.layout.modifiers;
+
+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.WireBuffer;
+import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/**
+ * Component size-aware background draw
+ */
+public class BackgroundModifierOperation extends DecoratorModifierOperation {
+
+ public static final BackgroundModifierOperation.Companion COMPANION =
+ new BackgroundModifierOperation.Companion();
+
+ float mX;
+ float mY;
+ float mWidth;
+ float mHeight;
+ float mR;
+ float mG;
+ float mB;
+ float mA;
+ int mShapeType = ShapeType.RECTANGLE;
+
+ public PaintBundle mPaint = new PaintBundle();
+
+ public BackgroundModifierOperation(float x, float y, float width, float height,
+ float r, float g, float b, float a,
+ int shapeType) {
+ this.mX = x;
+ this.mY = y;
+ this.mWidth = width;
+ this.mHeight = height;
+ this.mR = r;
+ this.mG = g;
+ this.mB = b;
+ this.mA = a;
+ this.mShapeType = shapeType;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mX, mY, mWidth, mHeight, mR, mG, mB, mA, mShapeType);
+ }
+
+ @Override
+ public void serializeToString(int indent, StringSerializer serializer) {
+ serializer.append(indent, "BACKGROUND = [" + mX + ", "
+ + mY + ", " + mWidth + ", " + mHeight
+ + "] color [" + mR + ", " + mG + ", " + mB + ", " + mA
+ + "] shape [" + mShapeType + "]");
+ }
+
+ @Override
+ public void layout(RemoteContext context, float width, float height) {
+ this.mWidth = width;
+ this.mHeight = height;
+ }
+
+ @Override
+ public String toString() {
+ return "BackgroundModifierOperation(" + mWidth + " x " + mHeight + ")";
+ }
+
+ public static class Companion implements CompanionOperation {
+
+
+ @Override
+ public String name() {
+ return "OrigamiBackground";
+ }
+
+ @Override
+ public int id() {
+ return Operations.MODIFIER_BACKGROUND;
+ }
+
+ public void apply(WireBuffer buffer, float x, float y, float width, float height,
+ float r, float g, float b, float a, int shapeType) {
+ buffer.start(Operations.MODIFIER_BACKGROUND);
+ buffer.writeFloat(x);
+ buffer.writeFloat(y);
+ buffer.writeFloat(width);
+ buffer.writeFloat(height);
+ buffer.writeFloat(r);
+ buffer.writeFloat(g);
+ buffer.writeFloat(b);
+ buffer.writeFloat(a);
+ // shape type
+ buffer.writeInt(shapeType);
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ float x = buffer.readFloat();
+ float y = buffer.readFloat();
+ float width = buffer.readFloat();
+ float height = buffer.readFloat();
+ float r = buffer.readFloat();
+ float g = buffer.readFloat();
+ float b = buffer.readFloat();
+ float a = buffer.readFloat();
+ // shape type
+ int shapeType = buffer.readInt();
+ operations.add(new BackgroundModifierOperation(x, y, width, height,
+ r, g, b, a, shapeType));
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.savePaint();
+ mPaint.reset();
+ mPaint.setColor(mR, mG, mB, mA);
+ context.applyPaint(mPaint);
+ if (mShapeType == ShapeType.RECTANGLE) {
+ context.drawRect(0f, 0f, mWidth, mHeight);
+ } else if (mShapeType == ShapeType.CIRCLE) {
+ context.drawCircle(mWidth / 2f, mHeight / 2f,
+ Math.min(mWidth, mHeight) / 2f);
+ }
+ context.restorePaint();
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java
new file mode 100644
index 0000000..0b9c01b
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout.modifiers;
+
+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.WireBuffer;
+import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/**
+ * Component size-aware border draw
+ */
+public class BorderModifierOperation extends DecoratorModifierOperation {
+
+ public static final BorderModifierOperation.Companion COMPANION =
+ new BorderModifierOperation.Companion();
+
+ float mX;
+ float mY;
+ float mWidth;
+ float mHeight;
+ float mBorderWidth;
+ float mRoundedCorner;
+ float mR;
+ float mG;
+ float mB;
+ float mA;
+ int mShapeType = ShapeType.RECTANGLE;
+
+ public PaintBundle paint = new PaintBundle();
+
+ public BorderModifierOperation(float x, float y, float width, float height,
+ float borderWidth, float roundedCorner,
+ float r, float g, float b, float a, int shapeType) {
+ this.mX = x;
+ this.mY = y;
+ this.mWidth = width;
+ this.mHeight = height;
+ this.mBorderWidth = borderWidth;
+ this.mRoundedCorner = roundedCorner;
+ this.mR = r;
+ this.mG = g;
+ this.mB = b;
+ this.mA = a;
+ this.mShapeType = shapeType;
+ }
+
+ @Override
+ public void serializeToString(int indent, StringSerializer serializer) {
+ serializer.append(indent, "BORDER = [" + mX + ", " + mY + ", "
+ + mWidth + ", " + mHeight + "] "
+ + "color [" + mR + ", " + mG + ", " + mB + ", " + mA + "] "
+ + "border [" + mBorderWidth + ", " + mRoundedCorner + "] "
+ + "shape [" + mShapeType + "]");
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mX, mY, mWidth, mHeight, mBorderWidth, mRoundedCorner,
+ mR, mG, mB, mA, mShapeType);
+ }
+
+ @Override
+ public void layout(RemoteContext context, float width, float height) {
+ this.mWidth = width;
+ this.mHeight = height;
+ }
+
+ @Override
+ public String toString() {
+ return "BorderModifierOperation(" + mX + "," + mY + " - " + mWidth + " x " + mHeight + ") "
+ + "borderWidth(" + mBorderWidth + ") "
+ + "color(" + mR + "," + mG + "," + mB + "," + mA + ")";
+ }
+
+ public static class Companion implements CompanionOperation {
+
+ @Override
+ public String name() {
+ return "BorderModifier";
+ }
+
+ @Override
+ public int id() {
+ return Operations.MODIFIER_BORDER;
+ }
+
+ public void apply(WireBuffer buffer, float x, float y, float width, float height,
+ float borderWidth, float roundedCorner,
+ float r, float g, float b, float a,
+ int shapeType) {
+ buffer.start(Operations.MODIFIER_BORDER);
+ buffer.writeFloat(x);
+ buffer.writeFloat(y);
+ buffer.writeFloat(width);
+ buffer.writeFloat(height);
+ buffer.writeFloat(borderWidth);
+ buffer.writeFloat(roundedCorner);
+ buffer.writeFloat(r);
+ buffer.writeFloat(g);
+ buffer.writeFloat(b);
+ buffer.writeFloat(a);
+ // shape type
+ buffer.writeInt(shapeType);
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ float x = buffer.readFloat();
+ float y = buffer.readFloat();
+ float width = buffer.readFloat();
+ float height = buffer.readFloat();
+ float bw = buffer.readFloat();
+ float rc = buffer.readFloat();
+ float r = buffer.readFloat();
+ float g = buffer.readFloat();
+ float b = buffer.readFloat();
+ float a = buffer.readFloat();
+ // shape type
+ int shapeType = buffer.readInt();
+ operations.add(new BorderModifierOperation(x, y, width, height, bw,
+ rc, r, g, b, a, shapeType));
+ }
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.savePaint();
+ paint.reset();
+ paint.setColor(mR, mG, mB, mA);
+ paint.setStrokeWidth(mBorderWidth);
+ paint.setStyle(PaintBundle.STYLE_STROKE);
+ context.applyPaint(paint);
+ if (mShapeType == ShapeType.RECTANGLE) {
+ context.drawRect(0f, 0f, mWidth, mHeight);
+ } else {
+ float size = mRoundedCorner;
+ if (mShapeType == ShapeType.CIRCLE) {
+ size = Math.min(mWidth, mHeight) / 2f;
+ }
+ context.drawRoundRect(0f, 0f, mWidth, mHeight, size, size);
+ }
+ context.restorePaint();
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java
new file mode 100644
index 0000000..30357af
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout.modifiers;
+
+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.WireBuffer;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/**
+ * Support modifier clip with a rectangle
+ */
+public class ClipRectModifierOperation extends DecoratorModifierOperation {
+
+ public static final ClipRectModifierOperation.Companion COMPANION =
+ new ClipRectModifierOperation.Companion();
+
+ float mWidth;
+ float mHeight;
+
+
+ @Override
+ public void paint(PaintContext context) {
+ context.clipRect(0f, 0f, mWidth, mHeight);
+ }
+
+ @Override
+ public void layout(RemoteContext context, float width, float height) {
+ this.mWidth = width;
+ this.mHeight = height;
+ }
+
+ @Override
+ public void onClick(float x, float y) {
+ // nothing
+ }
+
+ @Override
+ public void serializeToString(int indent, StringSerializer serializer) {
+ serializer.append(
+ indent, "CLIP_RECT = [" + mWidth + ", " + mHeight + "]");
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer);
+ }
+
+ public static class Companion implements CompanionOperation {
+
+ @Override
+ public String name() {
+ return "ClipRectModifier";
+ }
+
+ @Override
+ public int id() {
+ return Operations.MODIFIER_CLIP_RECT;
+ }
+
+ public void apply(WireBuffer buffer) {
+ buffer.start(Operations.MODIFIER_CLIP_RECT);
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ operations.add(new ClipRectModifierOperation());
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java
new file mode 100644
index 0000000..2ef0b9d
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java
@@ -0,0 +1,109 @@
+/*
+ * 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.layout.modifiers;
+
+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.WireBuffer;
+import com.android.internal.widget.remotecompose.core.operations.MatrixRestore;
+import com.android.internal.widget.remotecompose.core.operations.MatrixSave;
+import com.android.internal.widget.remotecompose.core.operations.layout.DecoratorComponent;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.ArrayList;
+
+/**
+ * Maintain a list of modifiers
+ */
+public class ComponentModifiers extends PaintOperation implements DecoratorComponent {
+ ArrayList<ModifierOperation> mList = new ArrayList<>();
+
+ public ArrayList<ModifierOperation> getList() {
+ return mList;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ // nothing
+ }
+
+ public void serializeToString(int indent, StringSerializer serializer) {
+ serializer.append(indent, "MODIFIERS");
+ for (ModifierOperation m : mList) {
+ m.serializeToString(indent + 1, serializer);
+ }
+ }
+
+ public void add(ModifierOperation operation) {
+ mList.add(operation);
+ }
+
+ public int size() {
+ return mList.size();
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ float tx = 0f;
+ float ty = 0f;
+ for (ModifierOperation op : mList) {
+ if (op instanceof PaddingModifierOperation) {
+ PaddingModifierOperation pop = (PaddingModifierOperation) op;
+ context.translate(pop.getLeft(), pop.getTop());
+ tx += pop.getLeft();
+ ty += pop.getTop();
+ }
+ if (op instanceof MatrixSave || op instanceof MatrixRestore) {
+ continue;
+ }
+ if (op instanceof PaintOperation) {
+ ((PaintOperation) op).paint(context);
+ }
+ }
+ // Back out the translates created by paddings
+ // TODO: we should be able to get rid of this when drawing the content of a component
+ context.translate(-tx, -ty);
+ }
+
+ @Override
+ public void layout(RemoteContext context, float width, float height) {
+ float w = width;
+ float h = height;
+ for (ModifierOperation op : mList) {
+ if (op instanceof PaddingModifierOperation) {
+ PaddingModifierOperation pop = (PaddingModifierOperation) op;
+ w -= pop.getLeft() + pop.getRight();
+ h -= pop.getTop() + pop.getBottom();
+ }
+ if (op instanceof DecoratorComponent) {
+ ((DecoratorComponent) op).layout(context, w, h);
+ }
+ }
+ }
+
+ public void addAll(ArrayList<ModifierOperation> operations) {
+ mList.addAll(operations);
+ }
+
+ public void onClick(float x, float y) {
+ for (ModifierOperation op : mList) {
+ if (op instanceof DecoratorComponent) {
+ ((DecoratorComponent) op).onClick(x, y);
+ }
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DecoratorModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DecoratorModifierOperation.java
new file mode 100644
index 0000000..bf9b27b
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DecoratorModifierOperation.java
@@ -0,0 +1,32 @@
+/*
+ * 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.layout.modifiers;
+
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.DecoratorComponent;
+
+/**
+ * Represents a decorator modifier (lightweight component), ie a modifier
+ * that impacts the visual output (background, border...)
+ */
+public abstract class DecoratorModifierOperation extends PaintOperation
+ implements ModifierOperation, DecoratorComponent {
+
+ @Override
+ public void onClick(float x, float y) {
+ // nothing
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java
new file mode 100644
index 0000000..04e9431
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java
@@ -0,0 +1,159 @@
+/*
+ * 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.layout.modifiers;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/**
+ * Base class for dimension modifiers
+ */
+public class DimensionModifierOperation implements ModifierOperation {
+
+ public static final DimensionModifierOperation.Companion COMPANION =
+ new DimensionModifierOperation.Companion(0, "DIMENSION");
+
+ public enum Type {
+ EXACT, FILL, WRAP, WEIGHT, INTRINSIC_MIN, INTRINSIC_MAX;
+
+ static Type fromInt(int value) {
+ switch (value) {
+ case 0: return EXACT;
+ case 1: return FILL;
+ case 2: return WRAP;
+ case 3: return WEIGHT;
+ case 4: return INTRINSIC_MIN;
+ case 5: return INTRINSIC_MAX;
+ }
+ return EXACT;
+ }
+ }
+
+ Type mType = Type.EXACT;
+ float mValue = Float.NaN;
+
+ public DimensionModifierOperation(Type type, float value) {
+ mType = type;
+ mValue = value;
+ }
+
+ public DimensionModifierOperation(Type type) {
+ this(type, Float.NaN);
+ }
+
+ public DimensionModifierOperation(float value) {
+ this(Type.EXACT, value);
+ }
+
+
+ public boolean hasWeight() {
+ return mType == Type.WEIGHT;
+ }
+
+ public boolean isWrap() {
+ return mType == Type.WRAP;
+ }
+
+ public boolean isFill() {
+ return mType == Type.FILL;
+ }
+
+ public Type getType() {
+ return mType;
+ }
+
+ public float getValue() {
+ return mValue;
+ }
+
+ public void setValue(float value) {
+ this.mValue = value;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mType.ordinal(), mValue);
+ }
+
+ public String serializedName() {
+ return "DIMENSION";
+ }
+
+ @Override
+ public void serializeToString(int indent, StringSerializer serializer) {
+ if (mType == Type.EXACT) {
+ serializer.append(indent, serializedName() + " = " + mValue);
+ }
+ }
+
+ @Override
+ public void apply(RemoteContext context) {
+ }
+
+ @Override
+ public String deepToString(String indent) {
+ return (indent != null ? indent : "") + toString();
+ }
+
+ @Override
+ public String toString() {
+ return "DimensionModifierOperation(" + mValue + ")";
+ }
+
+ public static class Companion implements CompanionOperation {
+
+ int mOperation;
+ String mName;
+
+ public Companion(int operation, String name) {
+ mOperation = operation;
+ mName = name;
+ }
+
+ @Override
+ public String name() {
+ return mName;
+ }
+
+ @Override
+ public int id() {
+ return mOperation;
+ }
+
+ public void apply(WireBuffer buffer, int type, float value) {
+ buffer.start(mOperation);
+ buffer.writeInt(type);
+ buffer.writeFloat(value);
+ }
+
+ public Operation construct(Type type, float value) {
+ return null;
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ Type type = Type.fromInt(buffer.readInt());
+ float value = buffer.readFloat();
+ Operation op = construct(type, value);
+ operations.add(op);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
new file mode 100644
index 0000000..81173c3
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
@@ -0,0 +1,55 @@
+/*
+ * 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.layout.modifiers;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+
+/**
+ * Set the height dimension on a component
+ */
+public class HeightModifierOperation extends DimensionModifierOperation {
+
+ public static final DimensionModifierOperation.Companion COMPANION =
+ new DimensionModifierOperation.Companion(Operations.MODIFIER_HEIGHT, "WIDTH") {
+ @Override
+ public Operation construct(DimensionModifierOperation.Type type, float value) {
+ return new HeightModifierOperation(type, value);
+ }
+ };
+
+ public HeightModifierOperation(Type type, float value) {
+ super(type, value);
+ }
+
+ public HeightModifierOperation(Type type) {
+ super(type);
+ }
+
+ public HeightModifierOperation(float value) {
+ super(value);
+ }
+
+ @Override
+ public String toString() {
+ return "Height(" + mValue + ")";
+ }
+
+ @Override
+ public String serializedName() {
+ return "HEIGHT";
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ModifierOperation.java
new file mode 100644
index 0000000..5299719
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ModifierOperation.java
@@ -0,0 +1,26 @@
+/*
+ * 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.layout.modifiers;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+/**
+ * Represents a modifier
+ */
+public interface ModifierOperation extends Operation {
+ void serializeToString(int indent, StringSerializer serializer);
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java
new file mode 100644
index 0000000..5ea6a97
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java
@@ -0,0 +1,135 @@
+/*
+ * 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.layout.modifiers;
+
+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 com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/**
+ * Represents a padding modifier.
+ * Padding modifiers can be chained and will impact following modifiers.
+ */
+public class PaddingModifierOperation implements ModifierOperation {
+
+ public static final PaddingModifierOperation.Companion COMPANION =
+ new PaddingModifierOperation.Companion();
+
+ float mLeft;
+ float mTop;
+ float mRight;
+ float mBottom;
+
+ public PaddingModifierOperation(float left, float top, float right, float bottom) {
+ this.mLeft = left;
+ this.mTop = top;
+ this.mRight = right;
+ this.mBottom = bottom;
+ }
+
+ public float getLeft() {
+ return mLeft;
+ }
+
+ public float getTop() {
+ return mTop;
+ }
+
+ public float getRight() {
+ return mRight;
+ }
+
+ public float getBottom() {
+ return mBottom;
+ }
+
+ public void setLeft(float left) {
+ this.mLeft = left;
+ }
+
+ public void setTop(float top) {
+ this.mTop = top;
+ }
+
+ public void setRight(float right) {
+ this.mRight = right;
+ }
+
+ public void setBottom(float bottom) {
+ this.mBottom = bottom;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mLeft, mTop, mRight, mBottom);
+ }
+
+ @Override
+ public void serializeToString(int indent, StringSerializer serializer) {
+ serializer.append(indent, "PADDING = [" + mLeft + ", " + mTop + ", "
+ + mRight + ", " + mBottom + "]");
+ }
+
+ @Override
+ public void apply(RemoteContext context) {
+ }
+
+ @Override
+ public String deepToString(String indent) {
+ return (indent != null ? indent : "") + toString();
+ }
+
+ @Override
+ public String toString() {
+ return "PaddingModifierOperation(" + mLeft + ", " + mTop
+ + ", " + mRight + ", " + mBottom + ")";
+ }
+
+ public static class Companion implements CompanionOperation {
+ @Override
+ public String name() {
+ return "PaddingModifierOperation";
+ }
+
+ @Override
+ public int id() {
+ return Operations.MODIFIER_PADDING;
+ }
+
+ public void apply(WireBuffer buffer,
+ float left, float top, float right, float bottom) {
+ buffer.start(Operations.MODIFIER_PADDING);
+ buffer.writeFloat(left);
+ buffer.writeFloat(top);
+ buffer.writeFloat(right);
+ buffer.writeFloat(bottom);
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ float left = buffer.readFloat();
+ float top = buffer.readFloat();
+ float right = buffer.readFloat();
+ float bottom = buffer.readFloat();
+ operations.add(new PaddingModifierOperation(left, top, right, bottom));
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java
new file mode 100644
index 0000000..9c57c6a
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout.modifiers;
+
+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.operations.DrawBase4;
+import com.android.internal.widget.remotecompose.core.operations.layout.DecoratorComponent;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+/**
+ * Support clip with a rectangle
+ */
+public class RoundedClipRectModifierOperation extends DrawBase4
+ implements ModifierOperation, DecoratorComponent {
+
+ public static final Companion COMPANION =
+ new Companion(Operations.MODIFIER_ROUNDED_CLIP_RECT) {
+ @Override
+ public Operation construct(float x1,
+ float y1,
+ float x2,
+ float y2) {
+ return new RoundedClipRectModifierOperation(x1, y1, x2, y2);
+ }
+ };
+ float mWidth;
+ float mHeight;
+
+
+ public RoundedClipRectModifierOperation(
+ float topStart,
+ float topEnd,
+ float bottomStart,
+ float bottomEnd) {
+ super(topStart, topEnd, bottomStart, bottomEnd);
+ mName = "ModifierRoundedClipRect";
+ }
+
+ @Override
+ public void paint(PaintContext context) {
+ context.roundedClipRect(mWidth, mHeight, mX1, mY1, mX2, mY2);
+ }
+
+ @Override
+ public void layout(RemoteContext context, float width, float height) {
+ this.mWidth = width;
+ this.mHeight = height;
+ }
+
+ @Override
+ public void onClick(float x, float y) {
+ // nothing
+ }
+
+ @Override
+ public void serializeToString(int indent, StringSerializer serializer) {
+ serializer.append(
+ indent, "ROUND_CLIP = [" + mWidth + ", " + mHeight
+ + ", " + mX1 + ", " + mY1
+ + ", " + mX2 + ", " + mY2 + "]");
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ShapeType.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ShapeType.java
new file mode 100644
index 0000000..e425b4e
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ShapeType.java
@@ -0,0 +1,25 @@
+/*
+ * 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.layout.modifiers;
+
+/**
+ * Known shapes, used for modifiers (clip/background/border)
+ */
+public class ShapeType {
+ public static int RECTANGLE = 0;
+ public static int CIRCLE = 1;
+ public static int ROUNDED_RECTANGLE = 2;
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
new file mode 100644
index 0000000..c46c8d7
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
@@ -0,0 +1,55 @@
+/*
+ * 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.layout.modifiers;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+
+/**
+ * Set the width dimension on a component
+ */
+public class WidthModifierOperation extends DimensionModifierOperation {
+
+ public static final DimensionModifierOperation.Companion COMPANION =
+ new DimensionModifierOperation.Companion(Operations.MODIFIER_WIDTH, "WIDTH") {
+ @Override
+ public Operation construct(DimensionModifierOperation.Type type, float value) {
+ return new WidthModifierOperation(type, value);
+ }
+ };
+
+ public WidthModifierOperation(Type type, float value) {
+ super(type, value);
+ }
+
+ public WidthModifierOperation(Type type) {
+ super(type);
+ }
+
+ public WidthModifierOperation(float value) {
+ super(value);
+ }
+
+ @Override
+ public String toString() {
+ return "Width(" + mValue + ")";
+ }
+
+ @Override
+ public String serializedName() {
+ return "WIDTH";
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/DebugLog.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/DebugLog.java
new file mode 100644
index 0000000..7ccf7f4
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/DebugLog.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout.utils;
+
+import java.util.ArrayList;
+
+/**
+ * Internal utility debug class
+ */
+public class DebugLog {
+
+ public static final boolean DEBUG_LAYOUT_ON = false;
+
+ public static class Node {
+ public Node parent;
+ public String name;
+ public String endString;
+ public ArrayList<Node> list = new ArrayList<>();
+
+ public Node(Node parent, String name) {
+ this.parent = parent;
+ this.name = name;
+ this.endString = name + " DONE";
+ if (parent != null) {
+ parent.add(this);
+ }
+ }
+
+ public void add(Node node) {
+ list.add(node);
+ }
+ }
+
+ public static class LogNode extends Node {
+ public LogNode(Node parent, String name) {
+ super(parent, name);
+ }
+ }
+
+ public static Node node = new Node(null, "Root");
+ public static Node currentNode = node;
+
+ public static void clear() {
+ node = new Node(null, "Root");
+ currentNode = node;
+ }
+
+ public static void s(StringValueSupplier valueSupplier) {
+ if (DEBUG_LAYOUT_ON) {
+ currentNode = new Node(currentNode, valueSupplier.getString());
+ }
+ }
+
+ public static void log(StringValueSupplier valueSupplier) {
+ if (DEBUG_LAYOUT_ON) {
+ new LogNode(currentNode, valueSupplier.getString());
+ }
+ }
+
+ public static void e() {
+ if (DEBUG_LAYOUT_ON) {
+ if (currentNode.parent != null) {
+ currentNode = currentNode.parent;
+ } else {
+ currentNode = node;
+ }
+ }
+ }
+
+ public static void e(StringValueSupplier valueSupplier) {
+ if (DEBUG_LAYOUT_ON) {
+ currentNode.endString = valueSupplier.getString();
+ if (currentNode.parent != null) {
+ currentNode = currentNode.parent;
+ } else {
+ currentNode = node;
+ }
+ }
+ }
+
+ public static void printNode(int indent, Node node, StringBuilder builder) {
+ if (DEBUG_LAYOUT_ON) {
+ StringBuilder indentationBuilder = new StringBuilder();
+ for (int i = 0; i < indent; i++) {
+ indentationBuilder.append("| ");
+ }
+ String indentation = indentationBuilder.toString();
+
+ if (node.list.size() > 0) {
+ builder.append(indentation).append(node.name).append("\n");
+ for (Node c : node.list) {
+ printNode(indent + 1, c, builder);
+ }
+ builder.append(indentation).append(node.endString).append("\n");
+ } else {
+ if (node instanceof LogNode) {
+ builder.append(indentation).append(" ").append(node.name).append("\n");
+ } else {
+ builder.append(indentation).append("-- ").append(node.name)
+ .append(" : ").append(node.endString).append("\n");
+ }
+ }
+ }
+ }
+
+ public static void display() {
+ if (DEBUG_LAYOUT_ON) {
+ StringBuilder builder = new StringBuilder();
+ printNode(0, node, builder);
+ System.out.println("\n" + builder.toString());
+ }
+ }
+}
+
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/StringValueSupplier.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/StringValueSupplier.java
new file mode 100644
index 0000000..79ef16b
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/StringValueSupplier.java
@@ -0,0 +1,27 @@
+/*
+ * 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.layout.utils;
+
+/**
+ * Basic interface for a lambda (used for logging)
+ */
+public interface StringValueSupplier {
+ /**
+ * returns a string value
+ * @return a string
+ */
+ String getString();
+}
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 a7d0ac6..8186192 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
@@ -695,6 +695,29 @@
}
/**
+ * Set the color based the R,G,B,A values
+ * @param r red (0 to 255)
+ * @param g green (0 to 255)
+ * @param b blue (0 to 255)
+ * @param a alpha (0 to 255)
+ */
+ public void setColor(int r, int g, int b, int a) {
+ int color = (a << 24) | (r << 16) | (g << 8) | b;
+ setColor(color);
+ }
+
+ /**
+ * Set the color based the R,G,B,A values
+ * @param r red (0.0 to 1.0)
+ * @param g green (0.0 to 1.0)
+ * @param b blue (0.0 to 1.0)
+ * @param a alpha (0.0 to 1.0)
+ */
+ public void setColor(float r, float g, float b, float a) {
+ setColor((int) r * 255, (int) g * 255, (int) b * 255, (int) a * 255);
+ }
+
+ /**
* Set the Color based on ID
* @param color
*/
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntFloatMap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntFloatMap.java
index 23c3ec5..b2d714e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntFloatMap.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntFloatMap.java
@@ -54,7 +54,7 @@
/**
* Put a item in the map
*
- * @param key item'values key
+ * @param key item's key
* @param value item's value
* @return old value if exist
*/
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntIntMap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntIntMap.java
index 221014c..606dc78 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntIntMap.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntIntMap.java
@@ -53,7 +53,7 @@
/**
* Put a item in the map
*
- * @param key item'values key
+ * @param key item's key
* @param value item's value
* @return old value if exist
*/
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java
index 4c1389c..a4fce80 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java
@@ -400,8 +400,7 @@
-1, // no op
2, 2, 2, 2, 2, // + - * / %
2, 2, 2, 2, 2, 2, 2, 2, 2, //<<, >> , >>> , | , &, ^, min max
- 1, 1, 1, 1, 1, 1, // neg, abs, ++, -- , not , sign
-
+ 1, 1, 1, 1, 1, 1, // neg, abs, ++, -- , not , sign
3, 3, 3, // clamp, ifElse, mad,
0, 0, 0 // mad, ?:,
// a[0],a[1],a[2]
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringSerializer.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringSerializer.java
new file mode 100644
index 0000000..fb90781
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringSerializer.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+/**
+ * Utility serializer maintaining an indent buffer
+ */
+public class StringSerializer {
+ StringBuffer mBuffer = new StringBuffer();
+ String mIndentBuffer = " ";
+
+ /**
+ * Append some content to the current buffer
+ * @param indent the indentation level to use
+ * @param content content to append
+ */
+ public void append(int indent, String content) {
+ String indentation = mIndentBuffer.substring(0, indent);
+ mBuffer.append(indentation);
+ mBuffer.append(indentation);
+ mBuffer.append(content);
+ mBuffer.append("\n");
+ }
+
+ /**
+ * Reset the buffer
+ */
+ public void reset() {
+ mBuffer = new StringBuffer();
+ }
+
+ /**
+ * Return a string representation of the buffer
+ * @return string representation
+ */
+ public String toString() {
+ return mBuffer.toString();
+ }
+}
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
index 693deaf..50a7d59 100644
--- 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
@@ -18,7 +18,7 @@
/**
* Provides and interface to create easing functions
*/
-public class GeneralEasing extends Easing{
+public class GeneralEasing extends Easing{
float[] mEasingData = new float[0];
Easing mEasingCurve = new CubicEasing(CUBIC_STANDARD);
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 a42c584..65a337e 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java
@@ -18,6 +18,7 @@
import com.android.internal.widget.remotecompose.core.CoreDocument;
import com.android.internal.widget.remotecompose.core.RemoteComposeBuffer;
import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
import java.io.InputStream;
@@ -113,5 +114,13 @@
return mDocument.getNamedColors();
}
+ /**
+ * Return a component associated with id
+ * @param id the component id
+ * @return the corresponding component or null if not found
+ */
+ public Component getComponent(int id) {
+ return mDocument.getComponent(id);
+ }
}
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 39a770a..e01dd17 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
@@ -65,6 +65,21 @@
this.mCanvas = canvas;
}
+ @Override
+ public void save() {
+ mCanvas.save();
+ }
+
+ @Override
+ public void saveLayer(float x, float y, float width, float height) {
+ mCanvas.saveLayer(x, y, x + width, y + height, mPaint);
+ }
+
+ @Override
+ public void restore() {
+ mCanvas.restore();
+ }
+
/**
* Draw an image onto the canvas
*
@@ -613,6 +628,19 @@
}
@Override
+ public void roundedClipRect(float width, float height,
+ float topStart, float topEnd,
+ float bottomStart, float bottomEnd) {
+ Path roundedPath = new Path();
+ float[] radii = new float[] { topStart, topStart,
+ topEnd, topEnd, bottomEnd, bottomEnd, bottomStart, bottomStart};
+
+ roundedPath.addRoundRect(0f, 0f, width, height,
+ radii, android.graphics.Path.Direction.CW);
+ mCanvas.clipPath(roundedPath);
+ }
+
+ @Override
public void clipPath(int pathId, int regionOp) {
Path path = getPath(pathId, 0, 1);
if (regionOp == ClipPath.DIFFERENCE) {
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 a2f79cc..0d7f97a 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
@@ -215,6 +215,7 @@
}
int w = measureDimension(widthMeasureSpec, mDocument.getWidth());
int h = measureDimension(heightMeasureSpec, mDocument.getHeight());
+ mDocument.getDocument().invalidateMeasure();
if (!USE_VIEW_AREA_CLICK) {
if (mDocument.getDocument().getContentSizing() == RootContentBehavior.SIZING_SCALE) {
@@ -235,6 +236,8 @@
if (mDocument == null) {
return;
}
+ mARContext.setAnimationEnabled(true);
+ mARContext.currentTime = System.currentTimeMillis();
mARContext.setDebug(mDebug);
mARContext.useCanvas(canvas);
mARContext.mWidth = getWidth();
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index ca984c0..2abdd57 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -258,6 +258,7 @@
"com_android_internal_content_om_OverlayManagerImpl.cpp",
"com_android_internal_net_NetworkUtilsInternal.cpp",
"com_android_internal_os_ClassLoaderFactory.cpp",
+ "com_android_internal_os_DebugStore.cpp",
"com_android_internal_os_FuseAppLoop.cpp",
"com_android_internal_os_KernelAllocationStats.cpp",
"com_android_internal_os_KernelCpuBpfTracking.cpp",
@@ -315,6 +316,7 @@
"libcrypto",
"libcutils",
"libdebuggerd_client",
+ "libdebugstore_cxx",
"libutils",
"libbinder",
"libbinderdebug",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index ed59327..03b5143a 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -202,6 +202,7 @@
extern int register_com_android_internal_content_om_OverlayManagerImpl(JNIEnv* env);
extern int register_com_android_internal_net_NetworkUtilsInternal(JNIEnv* env);
extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
+extern int register_com_android_internal_os_DebugStore(JNIEnv* env);
extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
extern int register_com_android_internal_os_KernelAllocationStats(JNIEnv* env);
extern int register_com_android_internal_os_KernelCpuBpfTracking(JNIEnv* env);
@@ -1599,6 +1600,7 @@
REG_JNI(register_com_android_internal_content_om_OverlayManagerImpl),
REG_JNI(register_com_android_internal_net_NetworkUtilsInternal),
REG_JNI(register_com_android_internal_os_ClassLoaderFactory),
+ REG_JNI(register_com_android_internal_os_DebugStore),
REG_JNI(register_com_android_internal_os_LongArrayMultiStateCounter),
REG_JNI(register_com_android_internal_os_LongMultiStateCounter),
REG_JNI(register_com_android_internal_os_Zygote),
diff --git a/core/jni/com_android_internal_os_DebugStore.cpp b/core/jni/com_android_internal_os_DebugStore.cpp
new file mode 100644
index 0000000..874d6ea
--- /dev/null
+++ b/core/jni/com_android_internal_os_DebugStore.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <debugstore/debugstore_cxx_bridge.rs.h>
+#include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedUtfChars.h>
+
+#include <iterator>
+#include <sstream>
+#include <vector>
+
+#include "core_jni_helpers.h"
+
+namespace android {
+
+static struct {
+ jmethodID mGet;
+ jmethodID mSize;
+} gListClassInfo;
+
+static std::vector<std::string> list_to_vector(JNIEnv* env, jobject jList) {
+ std::vector<std::string> vec;
+ jint size = env->CallIntMethod(jList, gListClassInfo.mSize);
+ if (size % 2 != 0) {
+ std::ostringstream oss;
+
+ std::copy(vec.begin(), vec.end(), std::ostream_iterator<std::string>(oss, ", "));
+ ALOGW("DebugStore list size is odd: %d, elements: %s", size, oss.str().c_str());
+
+ return vec;
+ }
+
+ vec.reserve(size);
+
+ for (jint i = 0; i < size; i++) {
+ ScopedLocalRef<jstring> jEntry(env,
+ reinterpret_cast<jstring>(
+ env->CallObjectMethod(jList, gListClassInfo.mGet,
+ i)));
+ ScopedUtfChars cEntry(env, jEntry.get());
+ vec.emplace_back(cEntry.c_str());
+ }
+ return vec;
+}
+
+static void com_android_internal_os_DebugStore_endEvent(JNIEnv* env, jclass clazz, jlong eventId,
+ jobject jAttributeList) {
+ auto attributes = list_to_vector(env, jAttributeList);
+ debugstore::debug_store_end(static_cast<uint64_t>(eventId), attributes);
+}
+
+static jlong com_android_internal_os_DebugStore_beginEvent(JNIEnv* env, jclass clazz,
+ jstring jeventName,
+ jobject jAttributeList) {
+ ScopedUtfChars eventName(env, jeventName);
+ auto attributes = list_to_vector(env, jAttributeList);
+ jlong eventId =
+ static_cast<jlong>(debugstore::debug_store_begin(eventName.c_str(), attributes));
+ return eventId;
+}
+
+static void com_android_internal_os_DebugStore_recordEvent(JNIEnv* env, jclass clazz,
+ jstring jeventName,
+ jobject jAttributeList) {
+ ScopedUtfChars eventName(env, jeventName);
+ auto attributes = list_to_vector(env, jAttributeList);
+ debugstore::debug_store_record(eventName.c_str(), attributes);
+}
+
+static const JNINativeMethod gDebugStoreMethods[] = {
+ /* name, signature, funcPtr */
+ {"beginEventNative", "(Ljava/lang/String;Ljava/util/List;)J",
+ (void*)com_android_internal_os_DebugStore_beginEvent},
+ {"endEventNative", "(JLjava/util/List;)V",
+ (void*)com_android_internal_os_DebugStore_endEvent},
+ {"recordEventNative", "(Ljava/lang/String;Ljava/util/List;)V",
+ (void*)com_android_internal_os_DebugStore_recordEvent},
+};
+
+int register_com_android_internal_os_DebugStore(JNIEnv* env) {
+ int res = RegisterMethodsOrDie(env, "com/android/internal/os/DebugStore", gDebugStoreMethods,
+ NELEM(gDebugStoreMethods));
+ jclass listClass = FindClassOrDie(env, "java/util/List");
+ gListClassInfo.mGet = GetMethodIDOrDie(env, listClass, "get", "(I)Ljava/lang/Object;");
+ gListClassInfo.mSize = GetMethodIDOrDie(env, listClass, "size", "()I");
+
+ return res;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/core/res/Android.bp b/core/res/Android.bp
index 9207aa8..e900eb2 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -164,6 +164,7 @@
"com.android.window.flags.window-aconfig",
"android.permission.flags-aconfig",
"android.os.flags-aconfig",
+ "android.media.tv.flags-aconfig",
],
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a00cc8b..50727a2 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5609,7 +5609,8 @@
@hide
-->
<permission android:name="android.permission.ALWAYS_BOUND_TV_INPUT"
- android:protectionLevel="signature|privileged|vendorPrivileged" />
+ android:protectionLevel="signature|privileged|vendorPrivileged"
+ android:featureFlag="android.media.tv.flags.tis_always_bound_permission"/>
<!-- Must be required by a {@link android.media.tv.interactive.TvInteractiveAppService}
to ensure that only the system can bind to it.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 495af5b..af0272e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3961,6 +3961,9 @@
flag does not exist -->
<bool name="config_magnification_always_on_enabled">true</bool>
+ <!-- Whether to keep fullscreen magnification zoom level when context changes. -->
+ <bool name="config_magnification_keep_zoom_level_when_context_changed">false</bool>
+
<!-- If true, the display will be shifted around in ambient mode. -->
<bool name="config_enableBurnInProtection">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b158e0f..8f4018f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4779,6 +4779,7 @@
<java-symbol type="bool" name="config_magnification_area" />
<java-symbol type="bool" name="config_magnification_always_on_enabled" />
+ <java-symbol type="bool" name="config_magnification_keep_zoom_level_when_context_changed" />
<java-symbol type="bool" name="config_trackerAppNeedsPermissions"/>
<!-- FullScreenMagnification thumbnail -->
diff --git a/core/tests/coretests/src/com/android/internal/os/DebugStoreTest.java b/core/tests/coretests/src/com/android/internal/os/DebugStoreTest.java
new file mode 100644
index 0000000..786c2fc
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/DebugStoreTest.java
@@ -0,0 +1,311 @@
+/*
+ * 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.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ServiceInfo;
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+/**
+ * Test class for {@link DebugStore}.
+ *
+ * To run it:
+ * atest FrameworksCoreTests:com.android.internal.os.DebugStoreTest
+ */
+@RunWith(AndroidJUnit4.class)
+@DisabledOnRavenwood(blockedBy = DebugStore.class)
+@SmallTest
+public class DebugStoreTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+ @Mock
+ private DebugStore.DebugStoreNative mDebugStoreNativeMock;
+
+ @Captor
+ private ArgumentCaptor<List<String>> mListCaptor;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ DebugStore.setDebugStoreNative(mDebugStoreNativeMock);
+ }
+
+ @Test
+ public void testRecordServiceOnStart() {
+ Intent intent = new Intent();
+ intent.setAction("com.android.ACTION");
+ intent.setComponent(new ComponentName("com.android", "androidService"));
+ intent.setPackage("com.android");
+
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(1L);
+
+ long eventId = DebugStore.recordServiceOnStart(1, 0, intent);
+
+ verify(mDebugStoreNativeMock).beginEvent(eq("SvcStart"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "stId", "1",
+ "flg", "0",
+ "act", "com.android.ACTION",
+ "comp", "ComponentInfo{com.android/androidService}",
+ "pkg", "com.android"
+ ).inOrder();
+ assertThat(eventId).isEqualTo(1L);
+ }
+
+ @Test
+ public void testRecordServiceCreate() {
+ ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.name = "androidService";
+ serviceInfo.packageName = "com.android";
+
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(2L);
+
+ long eventId = DebugStore.recordServiceCreate(serviceInfo);
+
+ verify(mDebugStoreNativeMock).beginEvent(eq("SvcCreate"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "name", "androidService",
+ "pkg", "com.android"
+ ).inOrder();
+ assertThat(eventId).isEqualTo(2L);
+ }
+
+ @Test
+ public void testRecordServiceBind() {
+ Intent intent = new Intent();
+ intent.setAction("com.android.ACTION");
+ intent.setComponent(new ComponentName("com.android", "androidService"));
+ intent.setPackage("com.android");
+
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(3L);
+
+ long eventId = DebugStore.recordServiceBind(true, intent);
+
+ verify(mDebugStoreNativeMock).beginEvent(eq("SvcBind"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "rebind", "true",
+ "act", "com.android.ACTION",
+ "cmp", "ComponentInfo{com.android/androidService}",
+ "pkg", "com.android"
+ ).inOrder();
+ assertThat(eventId).isEqualTo(3L);
+ }
+
+ @Test
+ public void testRecordGoAsync() {
+ DebugStore.recordGoAsync("androidReceiver");
+
+ verify(mDebugStoreNativeMock).recordEvent(eq("GoAsync"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "tname", Thread.currentThread().getName(),
+ "tid", String.valueOf(Thread.currentThread().getId()),
+ "rcv", "androidReceiver"
+ ).inOrder();
+ }
+
+ @Test
+ public void testRecordFinish() {
+ DebugStore.recordFinish("androidReceiver");
+
+ verify(mDebugStoreNativeMock).recordEvent(eq("Finish"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "tname", Thread.currentThread().getName(),
+ "tid", String.valueOf(Thread.currentThread().getId()),
+ "rcv", "androidReceiver"
+ ).inOrder();
+ }
+
+ @Test
+ public void testRecordLongLooperMessage() {
+ DebugStore.recordLongLooperMessage(100, "androidHandler", 500L);
+
+ verify(mDebugStoreNativeMock).recordEvent(eq("LooperMsg"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "code", "100",
+ "trgt", "androidHandler",
+ "elapsed", "500"
+ ).inOrder();
+ }
+
+ @Test
+ public void testRecordBroadcastHandleReceiver() {
+ Intent intent = new Intent();
+ intent.setAction("com.android.ACTION");
+ intent.setComponent(new ComponentName("com.android", "androidReceiver"));
+ intent.setPackage("com.android");
+
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(4L);
+
+ long eventId = DebugStore.recordBroadcastHandleReceiver(intent);
+
+ verify(mDebugStoreNativeMock).beginEvent(eq("HandleReceiver"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "tname", Thread.currentThread().getName(),
+ "tid", String.valueOf(Thread.currentThread().getId()),
+ "act", "com.android.ACTION",
+ "cmp", "ComponentInfo{com.android/androidReceiver}",
+ "pkg", "com.android"
+ ).inOrder();
+ assertThat(eventId).isEqualTo(4L);
+ }
+
+ @Test
+ public void testRecordEventEnd() {
+ DebugStore.recordEventEnd(1L);
+
+ verify(mDebugStoreNativeMock).endEvent(eq(1L), anyList());
+ }
+
+ @Test
+ public void testRecordServiceOnStartWithNullIntent() {
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(5L);
+
+ long eventId = DebugStore.recordServiceOnStart(1, 0, null);
+
+ verify(mDebugStoreNativeMock).beginEvent(eq("SvcStart"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "stId", "1",
+ "flg", "0",
+ "act", "null",
+ "comp", "null",
+ "pkg", "null"
+ ).inOrder();
+ assertThat(eventId).isEqualTo(5L);
+ }
+
+ @Test
+ public void testRecordServiceCreateWithNullServiceInfo() {
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(6L);
+
+ long eventId = DebugStore.recordServiceCreate(null);
+
+ verify(mDebugStoreNativeMock).beginEvent(eq("SvcCreate"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "name", "null",
+ "pkg", "null"
+ ).inOrder();
+ assertThat(eventId).isEqualTo(6L);
+ }
+
+ @Test
+ public void testRecordServiceBindWithNullIntent() {
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(7L);
+
+ long eventId = DebugStore.recordServiceBind(false, null);
+
+ verify(mDebugStoreNativeMock).beginEvent(eq("SvcBind"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "rebind", "false",
+ "act", "null",
+ "cmp", "null",
+ "pkg", "null"
+ ).inOrder();
+ assertThat(eventId).isEqualTo(7L);
+ }
+
+ @Test
+ public void testRecordBroadcastHandleReceiverWithNullIntent() {
+ when(mDebugStoreNativeMock.beginEvent(anyString(), anyList())).thenReturn(8L);
+
+ long eventId = DebugStore.recordBroadcastHandleReceiver(null);
+
+ verify(mDebugStoreNativeMock).beginEvent(eq("HandleReceiver"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "tname", Thread.currentThread().getName(),
+ "tid", String.valueOf(Thread.currentThread().getId()),
+ "act", "null",
+ "cmp", "null",
+ "pkg", "null"
+ ).inOrder();
+ assertThat(eventId).isEqualTo(8L);
+ }
+
+ @Test
+ public void testRecordGoAsyncWithNullReceiverClassName() {
+ DebugStore.recordGoAsync(null);
+
+ verify(mDebugStoreNativeMock).recordEvent(eq("GoAsync"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "tname", Thread.currentThread().getName(),
+ "tid", String.valueOf(Thread.currentThread().getId()),
+ "rcv", "null"
+ ).inOrder();
+ }
+
+ @Test
+ public void testRecordFinishWithNullReceiverClassName() {
+ DebugStore.recordFinish(null);
+
+ verify(mDebugStoreNativeMock).recordEvent(eq("Finish"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "tname", Thread.currentThread().getName(),
+ "tid", String.valueOf(Thread.currentThread().getId()),
+ "rcv", "null"
+ ).inOrder();
+ }
+
+ @Test
+ public void testRecordLongLooperMessageWithNullTargetClass() {
+ DebugStore.recordLongLooperMessage(200, null, 1000L);
+
+ verify(mDebugStoreNativeMock).recordEvent(eq("LooperMsg"), mListCaptor.capture());
+ List<String> capturedList = mListCaptor.getValue();
+ assertThat(capturedList).containsExactly(
+ "code", "200",
+ "trgt", "null",
+ "elapsed", "1000"
+ ).inOrder();
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
index b2bc3de..37f0067 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
@@ -18,8 +18,8 @@
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
-import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_UNKNOWN;
-import static androidx.window.common.CommonFoldingFeature.parseListFromString;
+import static androidx.window.common.layout.CommonFoldingFeature.COMMON_STATE_UNKNOWN;
+import static androidx.window.common.layout.CommonFoldingFeature.parseListFromString;
import android.annotation.NonNull;
import android.content.Context;
@@ -31,6 +31,9 @@
import android.util.Log;
import android.util.SparseIntArray;
+import androidx.window.common.layout.CommonFoldingFeature;
+import androidx.window.common.layout.DisplayFoldFeatureCommon;
+
import com.android.internal.R;
import java.util.ArrayList;
@@ -200,6 +203,23 @@
/**
+ * Returns the list of supported {@link DisplayFoldFeatureCommon} calculated from the
+ * {@link DeviceStateManagerFoldingFeatureProducer}.
+ */
+ @NonNull
+ public List<DisplayFoldFeatureCommon> getDisplayFeatures() {
+ final List<DisplayFoldFeatureCommon> foldFeatures = new ArrayList<>();
+ final List<CommonFoldingFeature> folds = getFoldsWithUnknownState();
+
+ final boolean isHalfOpenedSupported = isHalfOpenedSupported();
+ for (CommonFoldingFeature fold : folds) {
+ foldFeatures.add(DisplayFoldFeatureCommon.create(fold, isHalfOpenedSupported));
+ }
+ return foldFeatures;
+ }
+
+
+ /**
* Returns {@code true} if the device supports half-opened mode, {@code false} otherwise.
*/
public boolean isHalfOpenedSupported() {
@@ -211,7 +231,7 @@
* @param storeFeaturesConsumer a consumer to collect the data when it is first available.
*/
@Override
- public void getData(Consumer<List<CommonFoldingFeature>> storeFeaturesConsumer) {
+ public void getData(@NonNull Consumer<List<CommonFoldingFeature>> storeFeaturesConsumer) {
mRawFoldSupplier.getData((String displayFeaturesString) -> {
if (TextUtils.isEmpty(displayFeaturesString)) {
storeFeaturesConsumer.accept(new ArrayList<>());
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java
index 6d758f1..9651918 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java
@@ -26,6 +26,8 @@
import android.provider.Settings;
import android.text.TextUtils;
+import androidx.window.common.layout.CommonFoldingFeature;
+
import com.android.internal.R;
import java.util.Optional;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/collections/ListUtil.java b/libs/WindowManager/Jetpack/src/androidx/window/common/collections/ListUtil.java
new file mode 100644
index 0000000..e72459f
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/collections/ListUtil.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.common.collections;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+
+/**
+ * A class to contain utility methods for {@link List}.
+ */
+public final class ListUtil {
+
+ private ListUtil() {}
+
+ /**
+ * Returns a new {@link List} that is created by applying the {@code transformer} to the
+ * {@code source} list.
+ */
+ public static <T, U> List<U> map(List<T> source, Function<T, U> transformer) {
+ final List<U> target = new ArrayList<>();
+ for (int i = 0; i < source.size(); i++) {
+ target.add(transformer.apply(source.get(i)));
+ }
+ return target;
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/common/layout/CommonFoldingFeature.java
similarity index 99%
rename from libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
rename to libs/WindowManager/Jetpack/src/androidx/window/common/layout/CommonFoldingFeature.java
index b95bca1..85c4fe1 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/layout/CommonFoldingFeature.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package androidx.window.common;
+package androidx.window.common.layout;
import static androidx.window.common.ExtensionHelper.isZero;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/layout/DisplayFoldFeatureCommon.java b/libs/WindowManager/Jetpack/src/androidx/window/common/layout/DisplayFoldFeatureCommon.java
new file mode 100644
index 0000000..594bd9c
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/layout/DisplayFoldFeatureCommon.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 androidx.window.common.layout;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.util.ArraySet;
+
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A class that represents if a fold is part of the device.
+ */
+public final class DisplayFoldFeatureCommon {
+
+ /**
+ * Returns a new instance of {@link DisplayFoldFeatureCommon} based off of
+ * {@link CommonFoldingFeature} and whether or not half opened is supported.
+ */
+ public static DisplayFoldFeatureCommon create(CommonFoldingFeature foldingFeature,
+ boolean isHalfOpenedSupported) {
+ @FoldType
+ final int foldType;
+ if (foldingFeature.getType() == CommonFoldingFeature.COMMON_TYPE_HINGE) {
+ foldType = DISPLAY_FOLD_FEATURE_TYPE_HINGE;
+ } else {
+ foldType = DISPLAY_FOLD_FEATURE_TYPE_SCREEN_FOLD_IN;
+ }
+
+ final Set<Integer> properties = new ArraySet<>();
+
+ if (isHalfOpenedSupported) {
+ properties.add(DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED);
+ }
+ return new DisplayFoldFeatureCommon(foldType, properties);
+ }
+
+ /**
+ * The type of fold is unknown. This is here for compatibility reasons if a new type is added,
+ * and cannot be reported to an incompatible application.
+ */
+ public static final int DISPLAY_FOLD_FEATURE_TYPE_UNKNOWN = 0;
+
+ /**
+ * The type of fold is a physical hinge separating two display panels.
+ */
+ public static final int DISPLAY_FOLD_FEATURE_TYPE_HINGE = 1;
+
+ /**
+ * The type of fold is a screen that folds from 0-180.
+ */
+ public static final int DISPLAY_FOLD_FEATURE_TYPE_SCREEN_FOLD_IN = 2;
+
+ /**
+ * @hide
+ */
+ @IntDef(value = {DISPLAY_FOLD_FEATURE_TYPE_UNKNOWN, DISPLAY_FOLD_FEATURE_TYPE_HINGE,
+ DISPLAY_FOLD_FEATURE_TYPE_SCREEN_FOLD_IN})
+ public @interface FoldType {
+ }
+
+ /**
+ * The fold supports the half opened state.
+ */
+ public static final int DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED = 1;
+
+ @IntDef(value = {DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED})
+ public @interface FoldProperty {
+ }
+
+ @FoldType
+ private final int mType;
+
+ private final Set<Integer> mProperties;
+
+ /**
+ * Creates an instance of [FoldDisplayFeature].
+ *
+ * @param type the type of fold, either [FoldDisplayFeature.TYPE_HINGE] or
+ * [FoldDisplayFeature.TYPE_FOLDABLE_SCREEN]
+ * @hide
+ */
+ public DisplayFoldFeatureCommon(@FoldType int type, @NonNull Set<Integer> properties) {
+ mType = type;
+ mProperties = new ArraySet<>();
+ assertPropertiesAreValid(properties);
+ mProperties.addAll(properties);
+ }
+
+ /**
+ * Returns the type of fold that is either a hinge or a fold.
+ */
+ @FoldType
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Returns {@code true} if the fold has the given property, {@code false} otherwise.
+ */
+ public boolean hasProperty(@FoldProperty int property) {
+ return mProperties.contains(property);
+ }
+ /**
+ * Returns {@code true} if the fold has all the given properties, {@code false} otherwise.
+ */
+ public boolean hasProperties(@NonNull @FoldProperty int... properties) {
+ for (int i = 0; i < properties.length; i++) {
+ if (!mProperties.contains(properties[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns a copy of the set of properties.
+ * @hide
+ */
+ public Set<Integer> getProperties() {
+ return new ArraySet<>(mProperties);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ DisplayFoldFeatureCommon that = (DisplayFoldFeatureCommon) o;
+ return mType == that.mType && Objects.equals(mProperties, that.mProperties);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mType, mProperties);
+ }
+
+ @Override
+ public String toString() {
+ return "DisplayFoldFeatureCommon{mType=" + mType + ", mProperties=" + mProperties + '}';
+ }
+
+ private static void assertPropertiesAreValid(@NonNull Set<Integer> properties) {
+ for (int property : properties) {
+ if (!isProperty(property)) {
+ throw new IllegalArgumentException("Property is not a valid type: " + property);
+ }
+ }
+ }
+
+ private static boolean isProperty(int property) {
+ if (property == DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index e555176..7be14724 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -89,9 +89,9 @@
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.window.common.CommonFoldingFeature;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.common.EmptyLifecycleCallbacksAdapter;
+import androidx.window.common.layout.CommonFoldingFeature;
import androidx.window.extensions.WindowExtensions;
import androidx.window.extensions.core.util.function.Consumer;
import androidx.window.extensions.core.util.function.Function;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/DisplayFoldFeatureUtil.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/DisplayFoldFeatureUtil.java
index a0f481a..870c92e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/DisplayFoldFeatureUtil.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/DisplayFoldFeatureUtil.java
@@ -16,48 +16,24 @@
package androidx.window.extensions.layout;
-import androidx.window.common.CommonFoldingFeature;
-import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
-
-import java.util.ArrayList;
-import java.util.List;
+import androidx.window.common.layout.DisplayFoldFeatureCommon;
/**
* Util functions for working with {@link androidx.window.extensions.layout.DisplayFoldFeature}.
*/
-public class DisplayFoldFeatureUtil {
+public final class DisplayFoldFeatureUtil {
private DisplayFoldFeatureUtil() {}
- private static DisplayFoldFeature create(CommonFoldingFeature foldingFeature,
- boolean isHalfOpenedSupported) {
- final int foldType;
- if (foldingFeature.getType() == CommonFoldingFeature.COMMON_TYPE_HINGE) {
- foldType = DisplayFoldFeature.TYPE_HINGE;
- } else {
- foldType = DisplayFoldFeature.TYPE_SCREEN_FOLD_IN;
- }
- DisplayFoldFeature.Builder featureBuilder = new DisplayFoldFeature.Builder(foldType);
-
- if (isHalfOpenedSupported) {
- featureBuilder.addProperty(DisplayFoldFeature.FOLD_PROPERTY_SUPPORTS_HALF_OPENED);
- }
- return featureBuilder.build();
- }
-
/**
- * Returns the list of supported {@link DisplayFeature} calculated from the
- * {@link DeviceStateManagerFoldingFeatureProducer}.
+ * Returns a {@link DisplayFoldFeature} that matches the given {@link DisplayFoldFeatureCommon}.
*/
- public static List<DisplayFoldFeature> extractDisplayFoldFeatures(
- DeviceStateManagerFoldingFeatureProducer producer) {
- List<DisplayFoldFeature> foldFeatures = new ArrayList<>();
- List<CommonFoldingFeature> folds = producer.getFoldsWithUnknownState();
-
- final boolean isHalfOpenedSupported = producer.isHalfOpenedSupported();
- for (CommonFoldingFeature fold : folds) {
- foldFeatures.add(DisplayFoldFeatureUtil.create(fold, isHalfOpenedSupported));
+ public static DisplayFoldFeature translate(DisplayFoldFeatureCommon foldFeatureCommon) {
+ final DisplayFoldFeature.Builder builder =
+ new DisplayFoldFeature.Builder(foldFeatureCommon.getType());
+ for (int property: foldFeatureCommon.getProperties()) {
+ builder.addProperty(property);
}
- return foldFeatures;
+ return builder.build();
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index a3ef68a..f1ea19a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -19,11 +19,11 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
-import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_FLAT;
-import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_HALF_OPENED;
import static androidx.window.common.ExtensionHelper.isZero;
import static androidx.window.common.ExtensionHelper.rotateRectToDisplayRotation;
import static androidx.window.common.ExtensionHelper.transformToWindowSpaceRect;
+import static androidx.window.common.layout.CommonFoldingFeature.COMMON_STATE_FLAT;
+import static androidx.window.common.layout.CommonFoldingFeature.COMMON_STATE_HALF_OPENED;
import android.app.Activity;
import android.app.ActivityThread;
@@ -45,9 +45,10 @@
import androidx.annotation.Nullable;
import androidx.annotation.UiContext;
import androidx.annotation.VisibleForTesting;
-import androidx.window.common.CommonFoldingFeature;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.common.EmptyLifecycleCallbacksAdapter;
+import androidx.window.common.collections.ListUtil;
+import androidx.window.common.layout.CommonFoldingFeature;
import androidx.window.extensions.core.util.function.Consumer;
import androidx.window.extensions.util.DeduplicateConsumer;
@@ -95,8 +96,8 @@
.registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged());
mFoldingFeatureProducer = foldingFeatureProducer;
mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
- final List<DisplayFoldFeature> displayFoldFeatures =
- DisplayFoldFeatureUtil.extractDisplayFoldFeatures(mFoldingFeatureProducer);
+ final List<DisplayFoldFeature> displayFoldFeatures = ListUtil.map(
+ mFoldingFeatureProducer.getDisplayFeatures(), DisplayFoldFeatureUtil::translate);
mSupportedWindowFeatures = new SupportedWindowFeatures.Builder(displayFoldFeatures).build();
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
index b63fd08..60bc7be 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
@@ -26,10 +26,10 @@
import androidx.annotation.NonNull;
import androidx.window.common.BaseDataProducer;
-import androidx.window.common.CommonFoldingFeature;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.common.EmptyLifecycleCallbacksAdapter;
import androidx.window.common.RawFoldingFeatureProducer;
+import androidx.window.common.layout.CommonFoldingFeature;
import java.util.ArrayList;
import java.util.List;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java
index 4fd03e4..6e0e711 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java
@@ -26,7 +26,7 @@
import android.graphics.Rect;
import android.os.IBinder;
-import androidx.window.common.CommonFoldingFeature;
+import androidx.window.common.layout.CommonFoldingFeature;
import java.util.ArrayList;
import java.util.Collections;
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/common/collections/ListUtilTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/common/collections/ListUtilTest.java
new file mode 100644
index 0000000..a077bdf
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/common/collections/ListUtilTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.common.collections;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test class for {@link ListUtil}.
+ *
+ * Build/Install/Run:
+ * atest WMJetpackUnitTests:ListUtil
+ */
+public class ListUtilTest {
+
+ @Test
+ public void test_map_empty_returns_empty() {
+ final List<String> emptyList = new ArrayList<>();
+ final List<Integer> result = ListUtil.map(emptyList, String::length);
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ public void test_map_maintains_order() {
+ final List<String> source = new ArrayList<>();
+ source.add("a");
+ source.add("aa");
+
+ final List<Integer> result = ListUtil.map(source, String::length);
+
+ assertThat(result).containsExactly(1, 2).inOrder();
+ }
+}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/common/layout/DisplayFoldFeatureCommonTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/common/layout/DisplayFoldFeatureCommonTest.java
new file mode 100644
index 0000000..6c17851
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/common/layout/DisplayFoldFeatureCommonTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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 androidx.window.common.layout;
+
+import static androidx.window.common.layout.CommonFoldingFeature.COMMON_STATE_UNKNOWN;
+import static androidx.window.common.layout.CommonFoldingFeature.COMMON_TYPE_FOLD;
+import static androidx.window.common.layout.CommonFoldingFeature.COMMON_TYPE_HINGE;
+import static androidx.window.common.layout.DisplayFoldFeatureCommon.DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED;
+import static androidx.window.common.layout.DisplayFoldFeatureCommon.DISPLAY_FOLD_FEATURE_TYPE_HINGE;
+import static androidx.window.common.layout.DisplayFoldFeatureCommon.DISPLAY_FOLD_FEATURE_TYPE_SCREEN_FOLD_IN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.Rect;
+import android.util.ArraySet;
+
+import org.junit.Test;
+
+import java.util.Set;
+
+/**
+ * Test class for {@link DisplayFoldFeatureCommon}.
+ *
+ * Build/Install/Run:
+ * atest WMJetpackUnitTests:DisplayFoldFeatureCommonTest
+ */
+public class DisplayFoldFeatureCommonTest {
+
+ @Test
+ public void test_different_type_not_equals() {
+ final Set<Integer> properties = new ArraySet<>();
+ final DisplayFoldFeatureCommon first =
+ new DisplayFoldFeatureCommon(DISPLAY_FOLD_FEATURE_TYPE_HINGE, properties);
+ final DisplayFoldFeatureCommon second =
+ new DisplayFoldFeatureCommon(DISPLAY_FOLD_FEATURE_TYPE_SCREEN_FOLD_IN, properties);
+
+ assertThat(first).isEqualTo(second);
+ }
+
+ @Test
+ public void test_different_property_set_not_equals() {
+ final Set<Integer> firstProperties = new ArraySet<>();
+ final Set<Integer> secondProperties = new ArraySet<>();
+ secondProperties.add(DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED);
+ final DisplayFoldFeatureCommon first =
+ new DisplayFoldFeatureCommon(DISPLAY_FOLD_FEATURE_TYPE_HINGE, firstProperties);
+ final DisplayFoldFeatureCommon second =
+ new DisplayFoldFeatureCommon(DISPLAY_FOLD_FEATURE_TYPE_HINGE, secondProperties);
+
+ assertThat(first).isNotEqualTo(second);
+ }
+
+ @Test
+ public void test_check_single_property_exists() {
+ final Set<Integer> properties = new ArraySet<>();
+ properties.add(DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED);
+ final DisplayFoldFeatureCommon foldFeatureCommon =
+ new DisplayFoldFeatureCommon(DISPLAY_FOLD_FEATURE_TYPE_HINGE, properties);
+
+ assertThat(
+ foldFeatureCommon.hasProperty(DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED))
+ .isTrue();
+ }
+
+ @Test
+ public void test_check_multiple_properties_exists() {
+ final Set<Integer> properties = new ArraySet<>();
+ properties.add(DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED);
+ final DisplayFoldFeatureCommon foldFeatureCommon =
+ new DisplayFoldFeatureCommon(DISPLAY_FOLD_FEATURE_TYPE_HINGE, properties);
+
+ assertThat(foldFeatureCommon.hasProperties(
+ DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED))
+ .isTrue();
+ }
+
+ @Test
+ public void test_properties_matches_getter() {
+ final Set<Integer> properties = new ArraySet<>();
+ properties.add(DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED);
+ final DisplayFoldFeatureCommon foldFeatureCommon =
+ new DisplayFoldFeatureCommon(DISPLAY_FOLD_FEATURE_TYPE_HINGE, properties);
+
+ assertThat(foldFeatureCommon.getProperties()).isEqualTo(properties);
+ }
+
+ @Test
+ public void test_type_matches_getter() {
+ final Set<Integer> properties = new ArraySet<>();
+ final DisplayFoldFeatureCommon foldFeatureCommon =
+ new DisplayFoldFeatureCommon(DISPLAY_FOLD_FEATURE_TYPE_HINGE, properties);
+
+ assertThat(foldFeatureCommon.getType()).isEqualTo(DISPLAY_FOLD_FEATURE_TYPE_HINGE);
+ }
+
+ @Test
+ public void test_create_half_opened_feature() {
+ final CommonFoldingFeature foldingFeature =
+ new CommonFoldingFeature(COMMON_TYPE_HINGE, COMMON_STATE_UNKNOWN, new Rect());
+ final DisplayFoldFeatureCommon foldFeatureCommon = DisplayFoldFeatureCommon.create(
+ foldingFeature, true);
+
+ assertThat(foldFeatureCommon.getType()).isEqualTo(DISPLAY_FOLD_FEATURE_TYPE_HINGE);
+ assertThat(
+ foldFeatureCommon.hasProperty(DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED))
+ .isTrue();
+ }
+
+ @Test
+ public void test_create_fold_feature_no_half_opened() {
+ final CommonFoldingFeature foldingFeature =
+ new CommonFoldingFeature(COMMON_TYPE_FOLD, COMMON_STATE_UNKNOWN, new Rect());
+ final DisplayFoldFeatureCommon foldFeatureCommon = DisplayFoldFeatureCommon.create(
+ foldingFeature, true);
+
+ assertThat(foldFeatureCommon.getType()).isEqualTo(DISPLAY_FOLD_FEATURE_TYPE_SCREEN_FOLD_IN);
+ assertThat(
+ foldFeatureCommon.hasProperty(DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED))
+ .isTrue();
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
index 9fcf73d..026094c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -63,7 +63,8 @@
if (taskInfo.isResizeable) {
if (isFixedOrientationPortrait(topActivityInfo.screenOrientation)) {
// Respect apps fullscreen width
- Size(taskInfo.appCompatTaskInfo.topActivityLetterboxWidth, idealSize.height)
+ Size(taskInfo.appCompatTaskInfo.topActivityLetterboxAppWidth,
+ idealSize.height)
} else {
idealSize
}
@@ -79,7 +80,7 @@
// Respect apps fullscreen height and apply custom app width
Size(
customPortraitWidthForLandscapeApp,
- taskInfo.appCompatTaskInfo.topActivityLetterboxHeight
+ taskInfo.appCompatTaskInfo.topActivityLetterboxAppHeight
)
} else {
idealSize
@@ -143,9 +144,9 @@
/** Calculates the aspect ratio of an activity from its fullscreen bounds. */
fun calculateAspectRatio(taskInfo: RunningTaskInfo): Float {
+ val appLetterboxWidth = taskInfo.appCompatTaskInfo.topActivityLetterboxAppWidth
+ val appLetterboxHeight = taskInfo.appCompatTaskInfo.topActivityLetterboxAppHeight
if (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed) {
- val appLetterboxWidth = taskInfo.appCompatTaskInfo.topActivityLetterboxWidth
- val appLetterboxHeight = taskInfo.appCompatTaskInfo.topActivityLetterboxHeight
return maxOf(appLetterboxWidth, appLetterboxHeight) /
minOf(appLetterboxWidth, appLetterboxHeight).toFloat()
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 3e417b6..d1f557a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -949,9 +949,8 @@
val options =
ActivityOptions.makeBasic().apply {
launchWindowingMode = newTaskWindowingMode
- isPendingIntentBackgroundActivityLaunchAllowedByPermission = true
pendingIntentBackgroundActivityStartMode =
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
}
val launchIntent = PendingIntent.getActivity(
context,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 9de0651..401b78d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -30,6 +30,7 @@
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Color;
+import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
@@ -37,10 +38,12 @@
import android.os.Handler;
import android.util.Size;
import android.view.Choreographer;
+import android.view.InsetsState;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewConfiguration;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.window.WindowContainerTransaction;
@@ -195,7 +198,8 @@
RelayoutParams relayoutParams,
ActivityManager.RunningTaskInfo taskInfo,
boolean applyStartTransactionOnDraw,
- boolean setTaskCropAndPosition) {
+ boolean setTaskCropAndPosition,
+ InsetsState displayInsetsState) {
relayoutParams.reset();
relayoutParams.mRunningTaskInfo = taskInfo;
relayoutParams.mLayoutResId = R.layout.caption_window_decor;
@@ -223,6 +227,8 @@
controlsElement.mWidthResId = R.dimen.caption_right_buttons_width;
controlsElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.END;
relayoutParams.mOccludingCaptionElements.add(controlsElement);
+ relayoutParams.mCaptionTopPadding = getTopPadding(relayoutParams,
+ taskInfo.getConfiguration().windowConfiguration.getBounds(), displayInsetsState);
}
@SuppressLint("MissingPermission")
@@ -238,7 +244,7 @@
final WindowContainerTransaction wct = new WindowContainerTransaction();
updateRelayoutParams(mRelayoutParams, taskInfo, applyStartTransactionOnDraw,
- setTaskCropAndPosition);
+ setTaskCropAndPosition, mDisplayController.getInsetsState(taskInfo.displayId));
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
@@ -344,6 +350,18 @@
mDragResizeListener = null;
}
+ private static int getTopPadding(RelayoutParams params, Rect taskBounds,
+ InsetsState insetsState) {
+ if (!params.mRunningTaskInfo.isFreeform()) {
+ Insets systemDecor = insetsState.calculateInsets(taskBounds,
+ WindowInsets.Type.systemBars() & ~WindowInsets.Type.captionBar(),
+ false /* ignoreVisibility */);
+ return systemDecor.top;
+ } else {
+ return 0;
+ }
+ }
+
/**
* Checks whether the touch event falls inside the customizable caption region.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 81942e8..d68c018 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -89,6 +89,7 @@
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser;
+import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
@@ -175,6 +176,7 @@
private boolean mInImmersiveMode;
private final String mSysUIPackageName;
+ private final DisplayChangeController.OnDisplayChangingListener mOnDisplayChangingListener;
private final ISystemGestureExclusionListener mGestureExclusionListener =
new ISystemGestureExclusionListener.Stub() {
@Override
@@ -287,6 +289,31 @@
mSysUIPackageName = mContext.getResources().getString(
com.android.internal.R.string.config_systemUi);
mInteractionJankMonitor = interactionJankMonitor;
+ mOnDisplayChangingListener = (displayId, fromRotation, toRotation, displayAreaInfo, t) -> {
+ DesktopModeWindowDecoration decoration;
+ RunningTaskInfo taskInfo;
+ for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
+ decoration = mWindowDecorByTaskId.valueAt(i);
+ if (decoration == null) {
+ continue;
+ } else {
+ taskInfo = decoration.mTaskInfo;
+ }
+
+ // Check if display has been rotated between portrait & landscape
+ if (displayId == taskInfo.displayId && taskInfo.isFreeform()
+ && (fromRotation % 2 != toRotation % 2)) {
+ // Check if the task bounds on the rotated display will be out of bounds.
+ // If so, then update task bounds to be within reachable area.
+ final Rect taskBounds = new Rect(
+ taskInfo.configuration.windowConfiguration.getBounds());
+ if (DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(
+ taskBounds, decoration.calculateValidDragArea())) {
+ t.setBounds(taskInfo.token, taskBounds);
+ }
+ }
+ }
+ };
shellInit.addInitCallback(this::onInit, this);
}
@@ -298,6 +325,7 @@
new DesktopModeOnInsetsChangedListener());
mDesktopTasksController.setOnTaskResizeAnimationListener(
new DesktopModeOnTaskResizeAnimationListener());
+ mDisplayController.addDisplayChangingController(mOnDisplayChangingListener);
try {
mWindowManager.registerSystemGestureExclusionListener(mGestureExclusionListener,
mContext.getDisplayId());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 24fb971..d70e225 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -312,8 +312,7 @@
// transaction (that applies task crop) is synced with the buffer transaction (that draws
// the View). Both will be shown on screen at the same, whereas applying them independently
// causes flickering. See b/270202228.
- final boolean applyTransactionOnDraw =
- taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM;
+ final boolean applyTransactionOnDraw = taskInfo.isFreeform();
relayout(taskInfo, t, t, applyTransactionOnDraw, shouldSetTaskPositionAndCrop);
if (!applyTransactionOnDraw) {
t.apply();
@@ -324,7 +323,7 @@
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) {
Trace.beginSection("DesktopModeWindowDecoration#relayout");
- if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ if (taskInfo.isFreeform()) {
// The Task is in Freeform mode -> show its header in sync since it's an integral part
// of the window itself - a delayed header might cause bad UX.
relayoutInSync(taskInfo, startT, finishT, applyStartTransactionOnDraw,
@@ -524,9 +523,7 @@
}
private static boolean isDragResizable(ActivityManager.RunningTaskInfo taskInfo) {
- final boolean isFreeform =
- taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM;
- return isFreeform && taskInfo.isResizeable;
+ return taskInfo.isFreeform() && taskInfo.isResizeable;
}
private void updateMaximizeMenu(SurfaceControl.Transaction startT) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 4cab6e4..c15411b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -239,7 +239,8 @@
outResult.mHeight = taskBounds.height();
outResult.mRootView.setTaskFocusState(mTaskInfo.isFocused);
final Resources resources = mDecorWindowContext.getResources();
- outResult.mCaptionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
+ outResult.mCaptionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId)
+ + params.mCaptionTopPadding;
outResult.mCaptionWidth = params.mCaptionWidthId != Resources.ID_NULL
? loadDimensionPixelSize(resources, params.mCaptionWidthId) : taskBounds.width();
outResult.mCaptionX = (outResult.mWidth - outResult.mCaptionWidth) / 2;
@@ -459,6 +460,7 @@
}
mViewHost.getRootSurfaceControl().applyTransactionOnDraw(onDrawTransaction);
}
+ outResult.mRootView.setPadding(0, params.mCaptionTopPadding, 0, 0);
mViewHost.setView(outResult.mRootView, lp);
Trace.endSection();
} else {
@@ -469,6 +471,7 @@
}
mViewHost.getRootSurfaceControl().applyTransactionOnDraw(onDrawTransaction);
}
+ outResult.mRootView.setPadding(0, params.mCaptionTopPadding, 0, 0);
mViewHost.relayout(lp);
Trace.endSection();
}
@@ -700,6 +703,8 @@
int mShadowRadiusId;
int mCornerRadius;
+ int mCaptionTopPadding;
+
Configuration mWindowDecorConfig;
boolean mApplyStartTransactionOnDraw;
@@ -716,6 +721,8 @@
mShadowRadiusId = Resources.ID_NULL;
mCornerRadius = 0;
+ mCaptionTopPadding = 0;
+
mApplyStartTransactionOnDraw = false;
mSetTaskPositionAndCrop = false;
mWindowDecorConfig = null;
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/CloseAllAppsWithAppHeaderExitTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/CloseAllAppsWithAppHeaderExitTest.kt
new file mode 100644
index 0000000..9ba3a45
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/CloseAllAppsWithAppHeaderExitTest.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.CloseAllAppsWithAppHeaderExit
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/** Functional test for [CloseAllAppsWithAppHeaderExit]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class CloseAllAppsWithAppHeaderExitTest : CloseAllAppsWithAppHeaderExit() {
+
+ @Test
+ override fun closeAllAppsInDesktop() {
+ super.closeAllAppsInDesktop()
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/DragAppWindowMultiWindowTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/DragAppWindowMultiWindowTest.kt
new file mode 100644
index 0000000..ed1d488
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/DragAppWindowMultiWindowTest.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.DragAppWindowMultiWindow
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/** Functional test for [DragAppWindowMultiWindow]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class DragAppWindowMultiWindowTest : DragAppWindowMultiWindow()
+{
+ @Test
+ override fun dragAppWindow() {
+ super.dragAppWindow()
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/DragAppWindowSingleWindowTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/DragAppWindowSingleWindowTest.kt
new file mode 100644
index 0000000..d8b9348
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/DragAppWindowSingleWindowTest.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.DragAppWindowSingleWindow
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/** Functional test for [DragAppWindowSingleWindow]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class DragAppWindowSingleWindowTest : DragAppWindowSingleWindow()
+{
+ @Test
+ override fun dragAppWindow() {
+ super.dragAppWindow()
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/EnterDesktopWithAppHandleMenuTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/EnterDesktopWithAppHandleMenuTest.kt
new file mode 100644
index 0000000..546ce2d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/EnterDesktopWithAppHandleMenuTest.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.EnterDesktopWithAppHandleMenu
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/** Functional test for [EnterDesktopWithAppHandleMenu]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class EnterDesktopWithAppHandleMenuTest : EnterDesktopWithAppHandleMenu() {
+ @Test
+ override fun enterDesktopWithAppHandleMenu() {
+ super.enterDesktopWithAppHandleMenu()
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/EnterDesktopWithDragTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/EnterDesktopWithDragTest.kt
new file mode 100644
index 0000000..b5fdb16
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/EnterDesktopWithDragTest.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.functional
+
+import android.platform.test.annotations.Postsubmit
+import android.tools.Rotation
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.EnterDesktopWithDrag
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/** Functional test for [EnterDesktopWithDrag]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class EnterDesktopWithDragTest : EnterDesktopWithDrag(Rotation.ROTATION_0) {
+
+ @Test
+ override fun enterDesktopWithDrag() {
+ super.enterDesktopWithDrag()
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/ExitDesktopWithDragToTopDragZoneTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/ExitDesktopWithDragToTopDragZoneTest.kt
new file mode 100644
index 0000000..8f802d2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/ExitDesktopWithDragToTopDragZoneTest.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.functional
+
+import android.platform.test.annotations.Postsubmit
+import android.tools.Rotation
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.ExitDesktopWithDragToTopDragZone
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/** Functional test for [ExitDesktopWithDragToTopDragZone]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class ExitDesktopWithDragToTopDragZoneTest :
+ ExitDesktopWithDragToTopDragZone(Rotation.ROTATION_0) {
+ @Test
+ override fun exitDesktopWithDragToTopDragZone() {
+ super.exitDesktopWithDragToTopDragZone()
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/MaximizeAppWindowTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/MaximizeAppWindowTest.kt
new file mode 100644
index 0000000..f899082
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/MaximizeAppWindowTest.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.MaximizeAppWindow
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/** Functional test for [MaximizeAppWindow]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class MaximizeAppWindowTest : MaximizeAppWindow()
+{
+ @Test
+ override fun maximizeAppWindow() {
+ super.maximizeAppWindow()
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/MinimizeWindowOnAppOpenTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/MinimizeWindowOnAppOpenTest.kt
new file mode 100644
index 0000000..63c428a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/MinimizeWindowOnAppOpenTest.kt
@@ -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.wm.shell.flicker.service.desktopmode.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.MinimizeWindowOnAppOpen
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/** Functional test for [MinimizeWindowOnAppOpen]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class MinimizeWindowOnAppOpenTest : MinimizeWindowOnAppOpen()
+{
+ @Test
+ override fun openAppToMinimizeWindow() {
+ // Launch a new app while 4 apps are already open on desktop. This should result in the
+ // first app we opened to be minimized.
+ super.openAppToMinimizeWindow()
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/ResizeAppWithCornerResizeTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/ResizeAppWithCornerResizeTest.kt
new file mode 100644
index 0000000..4797aaf
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/ResizeAppWithCornerResizeTest.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.functional
+
+import android.platform.test.annotations.Postsubmit
+import android.tools.Rotation
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.ResizeAppWithCornerResize
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/** Functional test for [ResizeAppWithCornerResize]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class ResizeAppWithCornerResizeTest : ResizeAppWithCornerResize(Rotation.ROTATION_0) {
+ @Test
+ override fun resizeAppWithCornerResize() {
+ super.resizeAppWithCornerResize()
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/SwitchToOverviewFromDesktopTest.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/SwitchToOverviewFromDesktopTest.kt
new file mode 100644
index 0000000..9a71361
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/functional/SwitchToOverviewFromDesktopTest.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.service.desktopmode.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.SwitchToOverviewFromDesktop
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/** Functional test for [SwitchToOverviewFromDesktop]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class SwitchToOverviewFromDesktopTest : SwitchToOverviewFromDesktop() {
+ @Test
+ override fun switchToOverview() {
+ super.switchToOverview()
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index e6c72cd..0597951 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -2694,14 +2694,14 @@
screenOrientation == SCREEN_ORIENTATION_PORTRAIT) {
// Letterbox to portrait size
appCompatTaskInfo.topActivityBoundsLetterboxed = true
- appCompatTaskInfo.topActivityLetterboxWidth = 1200
- appCompatTaskInfo.topActivityLetterboxHeight = 1600
+ appCompatTaskInfo.topActivityLetterboxAppWidth = 1200
+ appCompatTaskInfo.topActivityLetterboxAppHeight = 1600
} else if (deviceOrientation == ORIENTATION_PORTRAIT &&
screenOrientation == SCREEN_ORIENTATION_LANDSCAPE) {
// Letterbox to landscape size
appCompatTaskInfo.topActivityBoundsLetterboxed = true
- appCompatTaskInfo.topActivityLetterboxWidth = 1600
- appCompatTaskInfo.topActivityLetterboxHeight = 1200
+ appCompatTaskInfo.topActivityLetterboxAppWidth = 1600
+ appCompatTaskInfo.topActivityLetterboxAppHeight = 1200
}
} else {
appCompatTaskInfo.topActivityBoundsLetterboxed = false
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt
index 261d4b5..d141c2d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt
@@ -21,6 +21,7 @@
import android.content.ComponentName
import android.testing.AndroidTestingRunner
import android.view.Display
+import android.view.InsetsState
import android.view.WindowInsetsController
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTestCase
@@ -45,7 +46,8 @@
relayoutParams,
taskInfo,
true,
- false
+ false,
+ InsetsState()
)
Truth.assertThat(relayoutParams.hasInputFeatureSpy()).isTrue()
@@ -63,7 +65,8 @@
relayoutParams,
taskInfo,
true,
- false
+ false,
+ InsetsState()
)
Truth.assertThat(relayoutParams.hasInputFeatureSpy()).isFalse()
@@ -77,7 +80,8 @@
relayoutParams,
taskInfo,
true,
- false
+ false,
+ InsetsState()
)
Truth.assertThat(relayoutParams.mOccludingCaptionElements.size).isEqualTo(2)
Truth.assertThat(relayoutParams.mOccludingCaptionElements[0].mAlignment).isEqualTo(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 61c7080..bbf42b5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -21,6 +21,7 @@
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.app.WindowConfiguration.WindowingMode
import android.content.ComponentName
@@ -51,11 +52,13 @@
import android.view.InsetsSource
import android.view.InsetsState
import android.view.KeyEvent
+import android.view.Surface
import android.view.SurfaceControl
import android.view.SurfaceView
import android.view.View
import android.view.WindowInsets.Type.navigationBars
import android.view.WindowInsets.Type.statusBars
+import android.window.WindowContainerTransaction
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
@@ -69,6 +72,7 @@
import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser
+import com.android.wm.shell.common.DisplayChangeController
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayInsetsController
import com.android.wm.shell.common.DisplayLayout
@@ -110,6 +114,7 @@
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doNothing
import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
@@ -166,6 +171,7 @@
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var shellInit: ShellInit
private lateinit var desktopModeOnInsetsChangedListener: DesktopModeOnInsetsChangedListener
+ private lateinit var displayChangingListener: DisplayChangeController.OnDisplayChangingListener
private lateinit var desktopModeWindowDecorViewModel: DesktopModeWindowDecorViewModel
@Before
@@ -174,6 +180,7 @@
mockitoSession()
.strictness(Strictness.LENIENT)
.spyStatic(DesktopModeStatus::class.java)
+ .spyStatic(DragPositioningCallbackUtility::class.java)
.startMocking()
doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(Mockito.any()) }
@@ -218,10 +225,17 @@
shellInit.init()
- val listenerCaptor =
- argumentCaptor<DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener>()
- verify(displayInsetsController).addInsetsChangedListener(anyInt(), listenerCaptor.capture())
- desktopModeOnInsetsChangedListener = listenerCaptor.firstValue
+ val insetListenerCaptor =
+ argumentCaptor<DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener>()
+ verify(displayInsetsController)
+ .addInsetsChangedListener(anyInt(), insetListenerCaptor.capture())
+ desktopModeOnInsetsChangedListener = insetListenerCaptor.firstValue
+
+ val displayChangingListenerCaptor =
+ argumentCaptor<DisplayChangeController.OnDisplayChangingListener>()
+ verify(mockDisplayController)
+ .addDisplayChangingController(displayChangingListenerCaptor.capture())
+ displayChangingListener = displayChangingListenerCaptor.firstValue
}
@After
@@ -786,6 +800,135 @@
})
}
+ @Test
+ fun testOnDisplayRotation_tasksOutOfValidArea_taskBoundsUpdated() {
+ val task = createTask(focused = true, windowingMode = WINDOWING_MODE_FREEFORM)
+ val secondTask =
+ createTask(displayId = task.displayId, windowingMode = WINDOWING_MODE_FREEFORM)
+ val thirdTask =
+ createTask(displayId = task.displayId, windowingMode = WINDOWING_MODE_FREEFORM)
+
+ doReturn(true).`when` {
+ DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(any(), any())
+ }
+ setUpMockDecorationsForTasks(task, secondTask, thirdTask)
+
+ onTaskOpening(task)
+ onTaskOpening(secondTask)
+ onTaskOpening(thirdTask)
+
+ val wct = mock<WindowContainerTransaction>()
+
+ displayChangingListener.onDisplayChange(
+ task.displayId, Surface.ROTATION_0, Surface.ROTATION_90, null, wct
+ )
+
+ verify(wct).setBounds(eq(task.token), any())
+ verify(wct).setBounds(eq(secondTask.token), any())
+ verify(wct).setBounds(eq(thirdTask.token), any())
+ }
+
+ @Test
+ fun testOnDisplayRotation_taskInValidArea_taskBoundsNotUpdated() {
+ val task = createTask(focused = true, windowingMode = WINDOWING_MODE_FREEFORM)
+ val secondTask =
+ createTask(displayId = task.displayId, windowingMode = WINDOWING_MODE_FREEFORM)
+ val thirdTask =
+ createTask(displayId = task.displayId, windowingMode = WINDOWING_MODE_FREEFORM)
+
+ doReturn(false).`when` {
+ DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(any(), any())
+ }
+ setUpMockDecorationsForTasks(task, secondTask, thirdTask)
+
+ onTaskOpening(task)
+ onTaskOpening(secondTask)
+ onTaskOpening(thirdTask)
+
+ val wct = mock<WindowContainerTransaction>()
+ displayChangingListener.onDisplayChange(
+ task.displayId, Surface.ROTATION_0, Surface.ROTATION_90, null, wct
+ )
+
+ verify(wct, never()).setBounds(eq(task.token), any())
+ verify(wct, never()).setBounds(eq(secondTask.token), any())
+ verify(wct, never()).setBounds(eq(thirdTask.token), any())
+ }
+
+ @Test
+ fun testOnDisplayRotation_sameOrientationRotation_taskBoundsNotUpdated() {
+ val task = createTask(focused = true, windowingMode = WINDOWING_MODE_FREEFORM)
+ val secondTask =
+ createTask(displayId = task.displayId, windowingMode = WINDOWING_MODE_FREEFORM)
+ val thirdTask =
+ createTask(displayId = task.displayId, windowingMode = WINDOWING_MODE_FREEFORM)
+
+ setUpMockDecorationsForTasks(task, secondTask, thirdTask)
+
+ onTaskOpening(task)
+ onTaskOpening(secondTask)
+ onTaskOpening(thirdTask)
+
+ val wct = mock<WindowContainerTransaction>()
+ displayChangingListener.onDisplayChange(
+ task.displayId, Surface.ROTATION_0, Surface.ROTATION_180, null, wct
+ )
+
+ verify(wct, never()).setBounds(eq(task.token), any())
+ verify(wct, never()).setBounds(eq(secondTask.token), any())
+ verify(wct, never()).setBounds(eq(thirdTask.token), any())
+ }
+
+ @Test
+ fun testOnDisplayRotation_differentDisplayId_taskBoundsNotUpdated() {
+ val task = createTask(focused = true, windowingMode = WINDOWING_MODE_FREEFORM)
+ val secondTask = createTask(displayId = -2, windowingMode = WINDOWING_MODE_FREEFORM)
+ val thirdTask = createTask(displayId = -3, windowingMode = WINDOWING_MODE_FREEFORM)
+
+ doReturn(true).`when` {
+ DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(any(), any())
+ }
+ setUpMockDecorationsForTasks(task, secondTask, thirdTask)
+
+ onTaskOpening(task)
+ onTaskOpening(secondTask)
+ onTaskOpening(thirdTask)
+
+ val wct = mock<WindowContainerTransaction>()
+ displayChangingListener.onDisplayChange(
+ task.displayId, Surface.ROTATION_0, Surface.ROTATION_90, null, wct
+ )
+
+ verify(wct).setBounds(eq(task.token), any())
+ verify(wct, never()).setBounds(eq(secondTask.token), any())
+ verify(wct, never()).setBounds(eq(thirdTask.token), any())
+ }
+
+ @Test
+ fun testOnDisplayRotation_nonFreeformTask_taskBoundsNotUpdated() {
+ val task = createTask(focused = true, windowingMode = WINDOWING_MODE_FREEFORM)
+ val secondTask = createTask(displayId = -2, windowingMode = WINDOWING_MODE_FULLSCREEN)
+ val thirdTask = createTask(displayId = -3, windowingMode = WINDOWING_MODE_PINNED)
+
+ doReturn(true).`when` {
+ DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(any(), any())
+ }
+ setUpMockDecorationsForTasks(task, secondTask, thirdTask)
+
+ onTaskOpening(task)
+ onTaskOpening(secondTask)
+ onTaskOpening(thirdTask)
+
+ val wct = mock<WindowContainerTransaction>()
+ displayChangingListener.onDisplayChange(
+ task.displayId, Surface.ROTATION_0, Surface.ROTATION_90, null, wct
+ )
+
+ verify(wct).setBounds(eq(task.token), any())
+ verify(wct, never()).setBounds(eq(secondTask.token), any())
+ verify(wct, never()).setBounds(eq(thirdTask.token), any())
+ }
+
private fun createOpenTaskDecoration(
@WindowingMode windowingMode: Int,
onMaxOrRestoreListenerCaptor: ArgumentCaptor<Function0<Unit>> =
@@ -864,6 +1007,7 @@
whenever(mockSplitScreenController.isTaskInSplitScreen(task.taskId))
.thenReturn(true)
}
+ whenever(decoration.calculateValidDragArea()).thenReturn(Rect(0, 60, 2560, 1600))
return decoration
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index e6e2d09..2ec3ab5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -867,6 +867,7 @@
final TestWindowDecoration windowDecor = createWindowDecoration(
new TestRunningTaskInfoBuilder().build());
mRelayoutParams.mApplyStartTransactionOnDraw = true;
+ mRelayoutResult.mRootView = mMockView;
windowDecor.updateViewHost(mRelayoutParams, mMockSurfaceControlStartT, mRelayoutResult);
@@ -878,6 +879,7 @@
final TestWindowDecoration windowDecor = createWindowDecoration(
new TestRunningTaskInfoBuilder().build());
mRelayoutParams.mApplyStartTransactionOnDraw = true;
+ mRelayoutResult.mRootView = mMockView;
assertThrows(IllegalArgumentException.class,
() -> windowDecor.updateViewHost(
@@ -889,6 +891,7 @@
final TestWindowDecoration windowDecor = createWindowDecoration(
new TestRunningTaskInfoBuilder().build());
mRelayoutParams.mApplyStartTransactionOnDraw = false;
+ mRelayoutResult.mRootView = mMockView;
windowDecor.updateViewHost(mRelayoutParams, null /* onDrawTransaction */, mRelayoutResult);
}
diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
index 97971e1..3196ba1 100644
--- a/media/java/android/media/tv/flags/media_tv.aconfig
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -23,4 +23,12 @@
namespace: "media_tv"
description: "TIAF V3.0 APIs for Android V"
bug: "303323657"
+}
+
+flag {
+ name: "tis_always_bound_permission"
+ is_exported: true
+ namespace: "media_tv"
+ description: "Introduce ALWAYS_BOUND_TV_INPUT for TIS."
+ bug: "332201346"
}
\ No newline at end of file
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index e8ef620..ba59ce8 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3264,6 +3264,24 @@
if (forceNotify || success) {
notifyForSettingsChange(key, name);
+
+ // If this is an aconfig flag, it will be written as a staged flag.
+ // Notify that its staged flag value will be updated.
+ if (Flags.notifyIndividualAconfigSyspropChanged() && type == SETTINGS_TYPE_CONFIG) {
+ int slashIndex = name.indexOf('/');
+ boolean validSlashIndex = slashIndex != -1
+ && slashIndex != 0
+ && slashIndex != name.length();
+ if (validSlashIndex) {
+ String namespace = name.substring(0, slashIndex);
+ String flagName = name.substring(slashIndex + 1);
+ if (settingsState.getAconfigDefaultFlags().containsKey(flagName)) {
+ String stagedName = "staged/" + namespace + "*" + flagName;
+ notifyForSettingsChange(key, stagedName);
+ }
+ }
+ }
+
if (wasUnsetNonPredefinedSetting) {
// Increment the generation number for all non-predefined, unset settings,
// because a new non-predefined setting has been inserted
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index 4f5955b..f53dec6 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -52,3 +52,14 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "notify_individual_aconfig_sysprop_changed"
+ namespace: "core_experiments_team_internal"
+ description: "When enabled, propagate individual aconfig sys props on flag stage."
+ bug: "331963764"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index f2c4a7f..201aaed 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1298,3 +1298,10 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "compose_haptic_sliders"
+ namespace: "systemui"
+ description: "Adding haptic component infrastructure to sliders in Compose."
+ bug: "341968766"
+}
\ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 7a41bc6..1255248 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -99,7 +99,9 @@
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
import org.mockito.kotlin.spy
+import org.mockito.kotlin.times
import org.mockito.kotlin.whenever
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
@@ -741,6 +743,18 @@
}
@Test
+ fun communalContent_readTriggersUmoVisibilityUpdate() =
+ testScope.runTest {
+ verify(mediaHost, never()).updateViewVisibility()
+
+ val communalContent by collectLastValue(underTest.communalContent)
+
+ // updateViewVisibility is called when the flow is collected.
+ assertThat(communalContent).isNotNull()
+ verify(mediaHost).updateViewVisibility()
+ }
+
+ @Test
fun scrollPosition_persistedOnEditEntry() {
val index = 2
val offset = 30
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt
index aef9163..b917014 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt
@@ -124,4 +124,36 @@
underTest.setIsLaunchingActivity(true)
Truth.assertThat(underTest.isLaunchingActivity.value).isEqualTo(true)
}
+
+ @Test
+ fun isAnyFlingAnimationRunning() =
+ testScope.runTest() {
+ val actual by collectLastValue(underTest.isAnyFlingAnimationRunning)
+
+ // WHEN transitioning from QS to Gone with user input ongoing
+ val userInputOngoing = MutableStateFlow(true)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Transition(
+ fromScene = Scenes.QuickSettings,
+ toScene = Scenes.Gone,
+ currentScene = flowOf(Scenes.QuickSettings),
+ progress = MutableStateFlow(.1f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = userInputOngoing,
+ )
+ )
+ sceneInteractor.setTransitionState(transitionState)
+ runCurrent()
+
+ // THEN qs is not flinging
+ Truth.assertThat(actual).isFalse()
+
+ // WHEN user input ends
+ userInputOngoing.value = false
+ runCurrent()
+
+ // THEN qs is flinging
+ Truth.assertThat(actual).isTrue()
+ }
}
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index fe49f3a..19eebf5 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3651,6 +3651,8 @@
hasn't typed in anything in the search box yet. The helper is a component that shows the
user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
<string name="shortcut_helper_search_placeholder">Search shortcuts</string>
+ <!-- Text shown when a search query didn't produce any results. [CHAR LIMIT=NONE] -->
+ <string name="shortcut_helper_no_search_results">No search results</string>
<!-- Content description of the icon that allows to collapse a keyboard shortcut helper category
panel. The helper is a component that shows the user which keyboard shortcuts they can
use. The helper shows shortcuts in categories, which can be collapsed or expanded.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
index ca03a00..da270c0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
@@ -39,7 +39,6 @@
import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations
import com.android.systemui.biometrics.shared.model.AuthenticationState
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
@@ -49,6 +48,7 @@
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
@@ -85,7 +85,7 @@
* onAcquired in [FingerprintManager.EnrollmentCallback] and [FaceManager.EnrollmentCallback]
*/
private val authenticationState: Flow<AuthenticationState> =
- conflatedCallbackFlow {
+ callbackFlow {
val updateAuthenticationState = { state: AuthenticationState ->
Log.d(TAG, "authenticationState updated: $state")
trySendWithFailureLogging(state, TAG, "Error sending AuthenticationState state")
@@ -169,7 +169,9 @@
}
}
- updateAuthenticationState(AuthenticationState.Idle(AuthenticationReason.NotRunning))
+ updateAuthenticationState(
+ AuthenticationState.Idle(requestReason = AuthenticationReason.NotRunning)
+ )
biometricManager?.registerAuthenticationStateListener(authenticationStateListener)
awaitClose {
biometricManager?.unregisterAuthenticationStateListener(
@@ -180,23 +182,32 @@
.distinctUntilChanged()
.shareIn(applicationScope, started = SharingStarted.Eagerly, replay = 1)
- override val fingerprintAuthenticationReason: Flow<AuthenticationReason> =
+ private val fingerprintAuthenticationState: Flow<AuthenticationState> =
authenticationState
.filter {
- it is AuthenticationState.Idle ||
- (it is AuthenticationState.Started &&
- it.biometricSourceType == BiometricSourceType.FINGERPRINT) ||
- (it is AuthenticationState.Stopped &&
- it.biometricSourceType == BiometricSourceType.FINGERPRINT)
+ it.biometricSourceType == null ||
+ it.biometricSourceType == BiometricSourceType.FINGERPRINT
}
+ .onEach { Log.d(TAG, "fingerprintAuthenticationState updated: $it") }
+
+ private val fingerprintRunningState: Flow<AuthenticationState> =
+ fingerprintAuthenticationState
+ .filter {
+ it is AuthenticationState.Idle ||
+ it is AuthenticationState.Started ||
+ it is AuthenticationState.Stopped
+ }
+ .onEach { Log.d(TAG, "fingerprintRunningState updated: $it") }
+
+ override val fingerprintAuthenticationReason: Flow<AuthenticationReason> =
+ fingerprintRunningState
.map { it.requestReason }
.onEach { Log.d(TAG, "fingerprintAuthenticationReason updated: $it") }
override val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus> =
- authenticationState
- .filterIsInstance<AuthenticationState.Acquired>()
- .filter { it.biometricSourceType == BiometricSourceType.FINGERPRINT }
- .map { AcquiredFingerprintAuthenticationStatus(it.requestReason, it.acquiredInfo) }
+ fingerprintAuthenticationState.filterIsInstance<AuthenticationState.Acquired>().map {
+ AcquiredFingerprintAuthenticationStatus(it.requestReason, it.acquiredInfo)
+ }
companion object {
private const val TAG = "BiometricStatusRepositoryImpl"
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationState.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationState.kt
index 5ceae36..81ea6a9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationState.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationState.kt
@@ -27,6 +27,9 @@
* authentication.
*/
sealed interface AuthenticationState {
+ /** Indicates [BiometricSourceType] of authentication state update, null in idle auth state. */
+ val biometricSourceType: BiometricSourceType?
+
/**
* Indicates [AuthenticationReason] from [BiometricRequestConstants.RequestReason] for
* requesting auth
@@ -43,7 +46,7 @@
* message.
*/
data class Acquired(
- val biometricSourceType: BiometricSourceType,
+ override val biometricSourceType: BiometricSourceType,
override val requestReason: AuthenticationReason,
val acquiredInfo: Int
) : AuthenticationState
@@ -59,7 +62,7 @@
* @param requestReason reason from [BiometricRequestConstants.RequestReason] for authentication
*/
data class Error(
- val biometricSourceType: BiometricSourceType,
+ override val biometricSourceType: BiometricSourceType,
val errString: String?,
val errCode: Int,
override val requestReason: AuthenticationReason,
@@ -73,7 +76,7 @@
* @param userId The user id for the requested authentication
*/
data class Failed(
- val biometricSourceType: BiometricSourceType,
+ override val biometricSourceType: BiometricSourceType,
override val requestReason: AuthenticationReason,
val userId: Int
) : AuthenticationState
@@ -87,7 +90,7 @@
* @param requestReason reason from [BiometricRequestConstants.RequestReason] for authentication
*/
data class Help(
- val biometricSourceType: BiometricSourceType,
+ override val biometricSourceType: BiometricSourceType,
val helpString: String?,
val helpCode: Int,
override val requestReason: AuthenticationReason,
@@ -96,9 +99,13 @@
/**
* Authentication state when no auth is running
*
+ * @param biometricSourceType null
* @param requestReason [AuthenticationReason.NotRunning]
*/
- data class Idle(override val requestReason: AuthenticationReason) : AuthenticationState
+ data class Idle(
+ override val biometricSourceType: BiometricSourceType? = null,
+ override val requestReason: AuthenticationReason
+ ) : AuthenticationState
/**
* AuthenticationState when auth is started
@@ -107,7 +114,7 @@
* @param requestReason reason from [BiometricRequestConstants.RequestReason] for authentication
*/
data class Started(
- val biometricSourceType: BiometricSourceType,
+ override val biometricSourceType: BiometricSourceType,
override val requestReason: AuthenticationReason
) : AuthenticationState
@@ -118,7 +125,7 @@
* @param requestReason [AuthenticationReason.NotRunning]
*/
data class Stopped(
- val biometricSourceType: BiometricSourceType,
+ override val biometricSourceType: BiometricSourceType,
override val requestReason: AuthenticationReason
) : AuthenticationState
@@ -131,7 +138,7 @@
* @param userId The user id for the requested authentication
*/
data class Succeeded(
- val biometricSourceType: BiometricSourceType,
+ override val biometricSourceType: BiometricSourceType,
val isStrongBiometric: Boolean,
override val requestReason: AuthenticationReason,
val userId: Int
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 3fc8b09..b06cf3f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -97,8 +97,10 @@
private val metricsLogger: CommunalMetricsLogger,
) : BaseCommunalViewModel(communalSceneInteractor, communalInteractor, mediaHost) {
+ private val logger = Logger(logBuffer, "CommunalViewModel")
+
private val _isMediaHostVisible =
- conflatedCallbackFlow<Boolean> {
+ conflatedCallbackFlow {
val callback = { visible: Boolean ->
trySend(visible)
Unit
@@ -106,11 +108,18 @@
mediaHost.addVisibilityChangeListener(callback)
awaitClose { mediaHost.removeVisibilityChangeListener(callback) }
}
- .onStart { emit(mediaHost.visible) }
+ .onStart {
+ // Ensure the visibility state is correct when the hub is opened and this flow is
+ // started so that the UMO is shown when needed. The visibility state in MediaHost
+ // is not updated once its view has been detached, aka the hub is closed, which can
+ // result in this getting stuck as False and never being updated as the UMO is not
+ // shown.
+ mediaHost.updateViewVisibility()
+ emit(mediaHost.visible)
+ }
+ .onEach { logger.d({ "_isMediaHostVisible: $bool1" }) { bool1 = it } }
.flowOn(mainDispatcher)
- private val logger = Logger(logBuffer, "CommunalViewModel")
-
/** Communal content saved from the previous emission when the flow is active (not "frozen"). */
private var frozenCommunalContent: List<CommunalContentModel>? = null
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index 67aedde..63f3d52 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -36,6 +36,7 @@
import androidx.compose.foundation.layout.FlowRowScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
@@ -208,9 +209,24 @@
Spacer(modifier = Modifier.height(6.dp))
ShortcutsSearchBar(onSearchQueryChanged)
Spacer(modifier = Modifier.height(16.dp))
- CategoriesPanelSinglePane(searchQuery, categories, selectedCategoryType, onCategorySelected)
- Spacer(modifier = Modifier.weight(1f))
- KeyboardSettings(onClick = onKeyboardSettingsClicked)
+ if (categories.isEmpty()) {
+ Box(modifier = Modifier.weight(1f)) {
+ NoSearchResultsText(horizontalPadding = 16.dp, fillHeight = true)
+ }
+ } else {
+ CategoriesPanelSinglePane(
+ searchQuery,
+ categories,
+ selectedCategoryType,
+ onCategorySelected
+ )
+ Spacer(modifier = Modifier.weight(1f))
+ }
+ KeyboardSettings(
+ horizontalPadding = 16.dp,
+ verticalPadding = 32.dp,
+ onClick = onKeyboardSettingsClicked
+ )
}
}
@@ -429,7 +445,7 @@
@Composable
private fun EndSidePanel(searchQuery: String, modifier: Modifier, category: ShortcutCategory?) {
if (category == null) {
- // TODO(b/353953351) - Show a "no results" UI?
+ NoSearchResultsText(horizontalPadding = 24.dp, fillHeight = false)
return
}
LazyColumn(modifier.nestedScroll(rememberNestedScrollInteropConnection())) {
@@ -441,6 +457,24 @@
}
@Composable
+private fun NoSearchResultsText(horizontalPadding: Dp, fillHeight: Boolean) {
+ var modifier = Modifier.fillMaxWidth()
+ if (fillHeight) {
+ modifier = modifier.fillMaxHeight()
+ }
+ Text(
+ stringResource(R.string.shortcut_helper_no_search_results),
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.onSurface,
+ modifier =
+ modifier
+ .padding(vertical = 8.dp)
+ .background(MaterialTheme.colorScheme.surfaceBright, RoundedCornerShape(28.dp))
+ .padding(horizontal = horizontalPadding, vertical = 24.dp)
+ )
+}
+
+@Composable
private fun SubCategoryContainerDualPane(searchQuery: String, subCategory: ShortcutSubCategory) {
Surface(
modifier = Modifier.fillMaxWidth(),
@@ -659,7 +693,11 @@
Spacer(modifier = Modifier.heightIn(8.dp))
CategoriesPanelTwoPane(categories, selectedCategory, onCategoryClicked)
Spacer(modifier = Modifier.weight(1f))
- KeyboardSettings(onKeyboardSettingsClicked)
+ KeyboardSettings(
+ horizontalPadding = 24.dp,
+ verticalPadding = 24.dp,
+ onKeyboardSettingsClicked
+ )
}
}
@@ -805,10 +843,9 @@
}
@Composable
-private fun KeyboardSettings(onClick: () -> Unit) {
+private fun KeyboardSettings(horizontalPadding: Dp, verticalPadding: Dp, onClick: () -> Unit) {
val interactionSource = remember { MutableInteractionSource() }
val isFocused by interactionSource.collectIsFocusedAsState()
-
Surface(
onClick = onClick,
shape = RoundedCornerShape(24.dp),
@@ -834,7 +871,7 @@
color = MaterialTheme.colorScheme.onSurfaceVariant,
fontSize = 16.sp
)
- Spacer(modifier = Modifier.width(8.dp))
+ Spacer(modifier = Modifier.weight(1f))
Icon(
imageVector = Icons.AutoMirrored.Default.OpenInNew,
contentDescription = null,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt
index 134c983..d1a0a6d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shade.domain.interactor
import com.android.systemui.shade.data.repository.ShadeAnimationRepository
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -38,4 +39,7 @@
* that is not considered "closing".
*/
abstract val isAnyCloseAnimationRunning: StateFlow<Boolean>
+
+ /** Whether a short animation to expand or collapse is running after user input has ended. */
+ abstract val isAnyFlingAnimationRunning: Flow<Boolean>
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt
index f364d6d..dbc1b3b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt
@@ -20,6 +20,7 @@
import com.android.systemui.shade.data.repository.ShadeAnimationRepository
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
/** Implementation of ShadeAnimationInteractor for shadeless SysUI variants. */
@SysUISingleton
@@ -29,4 +30,5 @@
shadeAnimationRepository: ShadeAnimationRepository,
) : ShadeAnimationInteractor(shadeAnimationRepository) {
override val isAnyCloseAnimationRunning = MutableStateFlow(false)
+ override val isAnyFlingAnimationRunning = flowOf(false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorLegacyImpl.kt
index c4f4134..32d8659 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorLegacyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorLegacyImpl.kt
@@ -20,6 +20,7 @@
import com.android.systemui.shade.data.repository.ShadeAnimationRepository
import com.android.systemui.shade.data.repository.ShadeRepository
import javax.inject.Inject
+import kotlinx.coroutines.flow.map
/** Implementation of ShadeAnimationInteractor compatible with NPVC. */
@SysUISingleton
@@ -30,4 +31,5 @@
shadeRepository: ShadeRepository,
) : ShadeAnimationInteractor(shadeAnimationRepository) {
override val isAnyCloseAnimationRunning = shadeRepository.legacyIsClosing
+ override val isAnyFlingAnimationRunning = shadeRepository.currentFling.map { it != null }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt
index d9982e3..79a94a5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt
@@ -36,12 +36,12 @@
@SysUISingleton
class ShadeAnimationInteractorSceneContainerImpl
@Inject
+@OptIn(ExperimentalCoroutinesApi::class)
constructor(
@Background scope: CoroutineScope,
shadeAnimationRepository: ShadeAnimationRepository,
sceneInteractor: SceneInteractor,
) : ShadeAnimationInteractor(shadeAnimationRepository) {
- @OptIn(ExperimentalCoroutinesApi::class)
override val isAnyCloseAnimationRunning =
sceneInteractor.transitionState
.flatMapLatest { state ->
@@ -62,4 +62,26 @@
}
.distinctUntilChanged()
.stateIn(scope, SharingStarted.Eagerly, false)
+
+ override val isAnyFlingAnimationRunning =
+ sceneInteractor.transitionState
+ .flatMapLatest { state ->
+ when (state) {
+ is ObservableTransitionState.Idle -> flowOf(false)
+ is ObservableTransitionState.Transition ->
+ if (
+ state.isInitiatedByUserInput &&
+ (state.fromScene == Scenes.Shade ||
+ state.toScene == Scenes.Shade ||
+ state.fromScene == Scenes.QuickSettings ||
+ state.toScene == Scenes.QuickSettings)
+ ) {
+ state.isUserInputOngoing.map { !it }
+ } else {
+ flowOf(false)
+ }
+ }
+ }
+ .distinctUntilChanged()
+ .stateIn(scope, SharingStarted.Eagerly, false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 37fdaeb..6d3cad5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2995,7 +2995,7 @@
@Override
public void onFalse() {
// Hides quick settings, bouncer, and quick-quick settings.
- mStatusBarKeyguardViewManager.reset(true, /* isFalsingReset= */true);
+ mStatusBarKeyguardViewManager.reset(true);
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 0b8f18e..2d775b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -708,7 +708,7 @@
* Shows the notification keyguard or the bouncer depending on
* {@link #needsFullscreenBouncer()}.
*/
- protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing, boolean isFalsingReset) {
+ protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing) {
boolean isDozing = mDozing;
if (Flags.simPinRaceConditionOnRestart()) {
KeyguardState toState = mKeyguardTransitionInteractor.getTransitionState().getValue()
@@ -734,12 +734,8 @@
mPrimaryBouncerInteractor.show(/* isScrimmed= */ true);
}
}
- } else if (!isFalsingReset) {
- // Falsing resets can cause this to flicker, so don't reset in this case
- Log.i(TAG, "Sim bouncer is already showing, issuing a refresh");
- mPrimaryBouncerInteractor.hide();
- mPrimaryBouncerInteractor.show(/* isScrimmed= */ true);
-
+ } else {
+ Log.e(TAG, "Attempted to show the sim bouncer when it is already showing.");
}
} else {
mCentralSurfaces.showKeyguard();
@@ -961,10 +957,6 @@
@Override
public void reset(boolean hideBouncerWhenShowing) {
- reset(hideBouncerWhenShowing, /* isFalsingReset= */false);
- }
-
- public void reset(boolean hideBouncerWhenShowing, boolean isFalsingReset) {
if (mKeyguardStateController.isShowing() && !bouncerIsAnimatingAway()) {
final boolean isOccluded = mKeyguardStateController.isOccluded();
// Hide quick settings.
@@ -976,7 +968,7 @@
hideBouncer(false /* destroyView */);
}
} else {
- showBouncerOrKeyguard(hideBouncerWhenShowing, isFalsingReset);
+ showBouncerOrKeyguard(hideBouncerWhenShowing);
}
if (hideBouncerWhenShowing) {
hideAlternateBouncer(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 9b61105..af5e60e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -1068,7 +1068,7 @@
public void testShowBouncerOrKeyguard_needsFullScreen() {
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
KeyguardSecurityModel.SecurityMode.SimPin);
- mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, false);
+ mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false);
verify(mCentralSurfaces).hideKeyguard();
verify(mPrimaryBouncerInteractor).show(true);
}
@@ -1084,7 +1084,7 @@
.thenReturn(KeyguardState.LOCKSCREEN);
reset(mCentralSurfaces);
- mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, false);
+ mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false);
verify(mPrimaryBouncerInteractor).show(true);
verify(mCentralSurfaces).showKeyguard();
}
@@ -1092,26 +1092,11 @@
@Test
@DisableSceneContainer
public void testShowBouncerOrKeyguard_needsFullScreen_bouncerAlreadyShowing() {
- boolean isFalsingReset = false;
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
KeyguardSecurityModel.SecurityMode.SimPin);
when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
- mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, isFalsingReset);
+ mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false);
verify(mCentralSurfaces, never()).hideKeyguard();
- verify(mPrimaryBouncerInteractor).show(true);
- }
-
- @Test
- @DisableSceneContainer
- public void testShowBouncerOrKeyguard_needsFullScreen_bouncerAlreadyShowing_onFalsing() {
- boolean isFalsingReset = true;
- when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
- KeyguardSecurityModel.SecurityMode.SimPin);
- when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
- mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, isFalsingReset);
- verify(mCentralSurfaces, never()).hideKeyguard();
-
- // Do not refresh the full screen bouncer if the call is from falsing
verify(mPrimaryBouncerInteractor, never()).show(true);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index b5b998f..6b6b39d 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -1131,7 +1131,10 @@
}
if (isAlwaysOnMagnificationEnabled()) {
- zoomOutFromService(displayId);
+ if (!mControllerCtx.getContext().getResources().getBoolean(
+ R.bool.config_magnification_keep_zoom_level_when_context_changed)) {
+ zoomOutFromService(displayId);
+ }
} else {
reset(displayId, true);
}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 33cf842..fdf0ba6 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -3535,6 +3535,10 @@
synchronized (mRecords) {
int phoneId = getPhoneIdFromSubId(subId);
+ if (!validatePhoneId(phoneId)) {
+ loge("Invalid phone ID " + phoneId + " for " + subId);
+ return;
+ }
mCarrierRoamingNtnMode[phoneId] = active;
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
@@ -3582,6 +3586,10 @@
synchronized (mRecords) {
int phoneId = getPhoneIdFromSubId(subId);
+ if (!validatePhoneId(phoneId)) {
+ loge("Invalid phone ID " + phoneId + " for " + subId);
+ return;
+ }
mCarrierRoamingNtnEligible[phoneId] = eligible;
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index e242164..e0aa9bf 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -21,11 +21,13 @@
import static com.android.server.grammaticalinflection.GrammaticalInflectionUtils.checkSystemGrammaticalGenderPermission;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.GrammaticalInflectionManager;
import android.app.IGrammaticalInflectionManager;
import android.content.AttributionSource;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.res.Configuration;
import android.os.Binder;
@@ -36,6 +38,7 @@
import android.os.ShellCallback;
import android.os.SystemProperties;
import android.os.Trace;
+import android.os.UserManager;
import android.permission.PermissionManager;
import android.util.AtomicFile;
import android.util.Log;
@@ -271,6 +274,31 @@
throw new IllegalArgumentException("Unknown grammatical gender");
}
+ // TODO(b/356895553): Don't allow profiles and background user to change system
+ // grammaticalinflection
+ if (UserManager.isVisibleBackgroundUsersEnabled()
+ && mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_AUTOMOTIVE)) {
+ // The check is added only for automotive devices. On automotive devices, it is
+ // possible that multiple users are visible simultaneously using visible background
+ // users. In such cases, it is desired that only the current user (not the visible
+ // background user) can change the GrammaticalInflection of the device.
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ int currentUser = ActivityManager.getCurrentUser();
+ if (userId != currentUser) {
+ Log.w(TAG,
+ "Only current user is allowed to update GrammaticalInflection if "
+ + "visible background users are enabled. Current User"
+ + currentUser + ". Calling User: " + userId);
+ throw new SecurityException("Only current user is allowed to update "
+ + "GrammaticalInflection.");
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
final File file = getGrammaticalGenderFile(userId);
synchronized (mLock) {
final AtomicFile atomicFile = new AtomicFile(file);
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index 42a99de..b67dd0f 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -220,7 +220,7 @@
@Override
public void onImeTargetOverlayVisibilityChanged(@NonNull IBinder overlayWindowToken,
@WindowManager.LayoutParams.WindowType int windowType, boolean visible,
- boolean removed) {
+ boolean removed, int displayId) {
// Ignoring the starting window since it's ok to cover the IME target
// window in temporary without affecting the IME visibility.
final boolean hasOverlay = visible && !removed
@@ -232,7 +232,7 @@
@Override
public void onImeInputTargetVisibilityChanged(IBinder imeInputTarget,
- boolean visibleRequested, boolean removed) {
+ boolean visibleRequested, boolean removed, int displayId) {
final boolean visibleAndNotRemoved = visibleRequested && !removed;
synchronized (ImfLock.class) {
if (visibleAndNotRemoved) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 76380b7..27dded9 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1001,6 +1001,8 @@
Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */);
ioThread.start();
+ SecureSettingsWrapper.setContentResolver(context.getContentResolver());
+
return new InputMethodManagerService(context,
shouldEnableConcurrentMultiUserMode(context), thread.getLooper(),
Handler.createAsync(ioThread.getLooper()),
@@ -1059,6 +1061,7 @@
public void onUserRemoved(UserInfo user) {
// Called directly from UserManagerService. Do not block the calling thread.
final int userId = user.id;
+ SecureSettingsWrapper.onUserRemoved(userId);
AdditionalSubtypeMapRepository.remove(userId);
InputMethodSettingsRepository.remove(userId);
mService.mUserDataRepository.remove(userId);
@@ -1185,7 +1188,6 @@
mConcurrentMultiUserModeEnabled = concurrentMultiUserModeEnabled;
mContext = context;
mRes = context.getResources();
- SecureSettingsWrapper.onStart(mContext);
mHandler = Handler.createAsync(uiLooper, this);
mIoHandler = ioHandler;
@@ -4356,7 +4358,6 @@
}
final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
- final boolean isCurrentUser = (mCurrentUserId == userId);
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final var newAdditionalSubtypeMap = settings.getNewAdditionalSubtypeMap(
imiId, toBeAdded, additionalSubtypeMap, mPackageManagerInternal, callingUid);
@@ -4370,10 +4371,7 @@
mUserManagerInternal.isUserUnlockingOrUnlocked(userId));
final var newSettings = InputMethodSettings.create(methodMap, userId);
InputMethodSettingsRepository.put(userId, newSettings);
- if (isCurrentUser) {
- postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */,
- userId);
- }
+ postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, userId);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -4401,17 +4399,14 @@
final long ident = Binder.clearCallingIdentity();
try {
synchronized (ImfLock.class) {
- final boolean currentUser = (mCurrentUserId == userId);
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
if (!settings.setEnabledInputMethodSubtypes(imeId, subtypeHashCodes)) {
return;
}
- if (currentUser) {
- // To avoid unnecessary "updateInputMethodsFromSettingsLocked" from happening.
- final var userData = getUserData(userId);
- userData.mLastEnabledInputMethodsStr = settings.getEnabledInputMethodsStr();
- updateInputMethodsFromSettingsLocked(false /* enabledChanged */, userId);
- }
+ // To avoid unnecessary "updateInputMethodsFromSettingsLocked" from happening.
+ final var userData = getUserData(userId);
+ userData.mLastEnabledInputMethodsStr = settings.getEnabledInputMethodsStr();
+ updateInputMethodsFromSettingsLocked(false /* enabledChanged */, userId);
}
} finally {
Binder.restoreCallingIdentity(ident);
diff --git a/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java b/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
index 476888e..3beec09 100644
--- a/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
+++ b/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
@@ -20,10 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
-import android.app.ActivityManagerInternal;
import android.content.ContentResolver;
-import android.content.Context;
-import android.content.pm.UserInfo;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -321,30 +318,13 @@
}
/**
- * Called when {@link InputMethodManagerService} is starting.
+ * Called when the system is starting.
*
- * @param context the {@link Context} to be used.
+ * @param contentResolver the {@link ContentResolver} to be used
*/
@AnyThread
- static void onStart(@NonNull Context context) {
- sContentResolver = context.getContentResolver();
-
- final int userId = LocalServices.getService(ActivityManagerInternal.class)
- .getCurrentUserId();
- final UserManagerInternal userManagerInternal =
- LocalServices.getService(UserManagerInternal.class);
- putOrGet(userId, createImpl(userManagerInternal, userId));
-
- userManagerInternal.addUserLifecycleListener(
- new UserManagerInternal.UserLifecycleListener() {
- @Override
- public void onUserRemoved(UserInfo user) {
- synchronized (sMutationLock) {
- sUserMap = sUserMap.cloneWithRemoveOrSelf(user.id);
- }
- }
- }
- );
+ static void setContentResolver(@NonNull ContentResolver contentResolver) {
+ sContentResolver = contentResolver;
}
/**
@@ -394,6 +374,18 @@
}
/**
+ * Called when a user is being removed.
+ *
+ * @param userId the ID of the user whose storage is being removed
+ */
+ @AnyThread
+ static void onUserRemoved(@UserIdInt int userId) {
+ synchronized (sMutationLock) {
+ sUserMap = sUserMap.cloneWithRemoveOrSelf(userId);
+ }
+ }
+
+ /**
* Put the given string {@code value} to {@code key}.
*
* @param key a secure settings key.
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index c95be17..21d6c64 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2725,11 +2725,16 @@
@Override
void onLongPress(long eventTime) {
- // Long-press should be triggered only if app doesn't handle it.
- mDeferredKeyActionExecutor.queueKeyAction(
- KeyEvent.KEYCODE_STEM_PRIMARY,
- eventTime,
- () -> stemPrimaryLongPress(eventTime));
+ if (mLongPressOnStemPrimaryBehavior == LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT) {
+ // Long-press to assistant gesture is not overridable by apps.
+ stemPrimaryLongPress(eventTime);
+ } else {
+ // Other long-press actions should be triggered only if app doesn't handle it.
+ mDeferredKeyActionExecutor.queueKeyAction(
+ KeyEvent.KEYCODE_STEM_PRIMARY,
+ eventTime,
+ () -> stemPrimaryLongPress(eventTime));
+ }
}
@Override
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index c21f783..331a594 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -1301,7 +1301,7 @@
final NetworkStats stats = getUidNetworkStatsSnapshotForTemplateLocked(
new NetworkTemplate.Builder(MATCH_PROXY).build(), /*includeTags=*/false);
if (stats != null) {
- ret.add(new NetworkStatsExt(sliceNetworkStatsByUidTagAndMetered(stats),
+ ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
new int[]{TRANSPORT_BLUETOOTH},
/*slicedByFgbg=*/true, /*slicedByTag=*/false,
/*slicedByMetered=*/false, TelephonyManager.NETWORK_TYPE_UNKNOWN,
diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
index aa63393..24ed1bb 100644
--- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java
+++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
@@ -23,7 +23,6 @@
import android.app.ActivityManager;
import android.graphics.Rect;
import android.os.Environment;
-import android.os.SystemProperties;
import android.os.Trace;
import android.util.ArraySet;
import android.util.IntArray;
@@ -33,7 +32,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
-import com.android.window.flags.Flags;
import java.io.File;
import java.io.PrintWriter;
@@ -109,7 +107,6 @@
!service.mContext
.getResources()
.getBoolean(com.android.internal.R.bool.config_disableTaskSnapshots)
- && isSnapshotEnabled()
&& !ActivityManager.isLowRamDeviceStatic(); // Don't support Android Go
setSnapshotEnabled(snapshotEnabled);
}
@@ -121,12 +118,6 @@
return Math.max(Math.min(config, 1f), 0.1f);
}
- // TODO remove when enabled
- static boolean isSnapshotEnabled() {
- return SystemProperties.getInt("persist.wm.debug.activity_screenshot", 0) != 0
- || Flags.activitySnapshotByDefault();
- }
-
static PersistInfoProvider createPersistInfoProvider(
WindowManagerService service, BaseAppSnapshotPersister.DirectoryResolver resolver) {
// Don't persist reduced file, instead we only persist the "HighRes" bitmap which has
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index 8c5193e..d98c2b3 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -89,13 +89,32 @@
return activityRecord.info.isChangeEnabled(overrideChangeId);
}
+ /**
+ * Attempts to return the app bounds (bounds without insets) of the top most opaque activity. If
+ * these are not available, it defaults to the bounds of the activity which include insets. In
+ * the event the activity is in Size Compat Mode, the Size Compat bounds are returned instead.
+ */
+ @NonNull
+ static Rect getAppBounds(@NonNull ActivityRecord activityRecord) {
+ // TODO(b/268458693): Refactor configuration inheritance in case of translucent activities
+ final Rect appBounds = activityRecord.getConfiguration().windowConfiguration.getAppBounds();
+ if (appBounds == null) {
+ return activityRecord.getBounds();
+ }
+ return activityRecord.mAppCompatController.getTransparentPolicy()
+ .findOpaqueNotFinishingActivityBelow()
+ .map(AppCompatUtils::getAppBounds)
+ .orElseGet(() -> {
+ if (activityRecord.hasSizeCompatBounds()) {
+ return activityRecord.getScreenResolvedBounds();
+ }
+ return appBounds;
+ });
+ }
+
static void fillAppCompatTaskInfo(@NonNull Task task, @NonNull TaskInfo info,
@Nullable ActivityRecord top) {
final AppCompatTaskInfo appCompatTaskInfo = info.appCompatTaskInfo;
- appCompatTaskInfo.topActivityLetterboxVerticalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
- appCompatTaskInfo.topActivityLetterboxHorizontalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
- appCompatTaskInfo.topActivityLetterboxWidth = TaskInfo.PROPERTY_VALUE_UNSET;
- appCompatTaskInfo.topActivityLetterboxHeight = TaskInfo.PROPERTY_VALUE_UNSET;
appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode =
CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE;
if (top == null) {
@@ -124,8 +143,13 @@
.getAppCompatAspectRatioOverrides().isSystemOverrideToFullscreenEnabled();
appCompatTaskInfo.isFromLetterboxDoubleTap = reachabilityOverrides.isFromDoubleTap();
- appCompatTaskInfo.topActivityLetterboxWidth = top.getBounds().width();
- appCompatTaskInfo.topActivityLetterboxHeight = top.getBounds().height();
+ final Rect bounds = top.getBounds();
+ final Rect appBounds = getAppBounds(top);
+ appCompatTaskInfo.topActivityLetterboxWidth = bounds.width();
+ appCompatTaskInfo.topActivityLetterboxHeight = bounds.height();
+ appCompatTaskInfo.topActivityLetterboxAppWidth = appBounds.width();
+ appCompatTaskInfo.topActivityLetterboxAppHeight = appBounds.height();
+
// We need to consider if letterboxed or pillarboxed.
// TODO(b/336807329) Encapsulate reachability logic
appCompatTaskInfo.isLetterboxDoubleTapEnabled = reachabilityOverrides
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 924f765..48e1079 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -963,8 +963,7 @@
mWindowManagerService = wms;
final Context context = wms.mContext;
mShowWindowlessSurface = context.getResources().getBoolean(
- com.android.internal.R.bool.config_predictShowStartingSurface)
- && Flags.activitySnapshotByDefault();
+ com.android.internal.R.bool.config_predictShowStartingSurface);
}
private static final int UNKNOWN = 0;
private static final int TASK_SWITCH = 1;
diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
index 9996bbc..3e55e2d 100644
--- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
+++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
@@ -234,12 +234,13 @@
float desiredAspectRatio = 0;
if (taskInfo.isRunning) {
final AppCompatTaskInfo appCompatTaskInfo = taskInfo.appCompatTaskInfo;
+ final int appLetterboxWidth =
+ taskInfo.appCompatTaskInfo.topActivityLetterboxAppWidth;
+ final int appLetterboxHeight =
+ taskInfo.appCompatTaskInfo.topActivityLetterboxAppHeight;
if (appCompatTaskInfo.topActivityBoundsLetterboxed) {
- desiredAspectRatio = (float) Math.max(
- appCompatTaskInfo.topActivityLetterboxWidth,
- appCompatTaskInfo.topActivityLetterboxHeight)
- / Math.min(appCompatTaskInfo.topActivityLetterboxWidth,
- appCompatTaskInfo.topActivityLetterboxHeight);
+ desiredAspectRatio = (float) Math.max(appLetterboxWidth, appLetterboxHeight)
+ / Math.min(appLetterboxWidth, appLetterboxHeight);
} else {
desiredAspectRatio = Math.max(fullscreenHeight, fullscreenWidth)
/ Math.min(fullscreenHeight, fullscreenWidth);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 3a0de85..9c8c759 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4415,13 +4415,14 @@
mWmService.dispatchImeInputTargetVisibilityChanged(
targetWin.mClient.asBinder(), isVisibleRequested,
targetWin.mActivityRecord != null
- && targetWin.mActivityRecord.finishing);
+ && targetWin.mActivityRecord.finishing,
+ mDisplayId);
}
});
targetWin.mToken.registerWindowContainerListener(
mImeTargetTokenListenerPair.second);
mWmService.dispatchImeInputTargetVisibilityChanged(targetWin.mClient.asBinder(),
- targetWin.isVisible() /* visible */, false /* removed */);
+ targetWin.isVisible() /* visible */, false /* removed */, mDisplayId);
}
}
if (refreshImeSecureFlag(getPendingTransaction())) {
diff --git a/services/core/java/com/android/server/wm/ImeTargetChangeListener.java b/services/core/java/com/android/server/wm/ImeTargetChangeListener.java
index 88b76aa..e94f17c 100644
--- a/services/core/java/com/android/server/wm/ImeTargetChangeListener.java
+++ b/services/core/java/com/android/server/wm/ImeTargetChangeListener.java
@@ -37,25 +37,27 @@
* @param visible the visibility of the overlay window, {@code true} means visible
* and {@code false} otherwise.
* @param removed Whether the IME target overlay window has being removed.
+ * @param displayId display ID where the overlay window exists.
*/
default void onImeTargetOverlayVisibilityChanged(@NonNull IBinder overlayWindowToken,
@WindowManager.LayoutParams.WindowType int windowType,
- boolean visible, boolean removed) {
+ boolean visible, boolean removed, int displayId) {
}
/**
* Called when the visibility of IME input target window has changed.
*
* @param imeInputTarget the window token of the IME input target window.
- * @param visible the new window visibility made by {@param imeInputTarget}. visible is
+ * @param visible the new window visibility made by {@code imeInputTarget}. visible is
* {@code true} when switching to the new visible IME input target
* window and started input, or the same input target relayout to
* visible from invisible. In contrast, visible is {@code false} when
* closing the input target, or the same input target relayout to
* invisible from visible.
* @param removed Whether the IME input target window has being removed.
+ * @param displayId display ID where the overlay window exists.
*/
default void onImeInputTargetVisibilityChanged(@NonNull IBinder imeInputTarget, boolean visible,
- boolean removed) {
+ boolean removed, int displayId) {
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d73d509..cf92f1b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1897,7 +1897,8 @@
displayContent.computeImeTarget(true /* updateImeTarget */);
if (win.isImeOverlayLayeringTarget()) {
dispatchImeTargetOverlayVisibilityChanged(client.asBinder(), win.mAttrs.type,
- win.isVisibleRequestedOrAdding(), false /* removed */);
+ win.isVisibleRequestedOrAdding(), false /* removed */,
+ displayContent.getDisplayId());
}
}
@@ -2661,13 +2662,13 @@
final boolean winVisibleChanged = win.isVisible() != wasVisible;
if (win.isImeOverlayLayeringTarget() && winVisibleChanged) {
dispatchImeTargetOverlayVisibilityChanged(client.asBinder(), win.mAttrs.type,
- win.isVisible(), false /* removed */);
+ win.isVisible(), false /* removed */, win.getDisplayId());
}
// Notify listeners about IME input target window visibility change.
final boolean isImeInputTarget = win.getDisplayContent().getImeInputTarget() == win;
if (isImeInputTarget && winVisibleChanged) {
dispatchImeInputTargetVisibilityChanged(win.mClient.asBinder(),
- win.isVisible() /* visible */, false /* removed */);
+ win.isVisible() /* visible */, false /* removed */, win.getDisplayId());
}
if (outRelayoutResult != null) {
@@ -3515,27 +3516,29 @@
void dispatchImeTargetOverlayVisibilityChanged(@NonNull IBinder token,
@WindowManager.LayoutParams.WindowType int windowType, boolean visible,
- boolean removed) {
+ boolean removed, int displayId) {
if (mImeTargetChangeListener != null) {
if (DEBUG_INPUT_METHOD) {
Slog.d(TAG, "onImeTargetOverlayVisibilityChanged, win=" + mWindowMap.get(token)
+ ", type=" + ViewDebug.intToString(WindowManager.LayoutParams.class,
- "type", windowType) + "visible=" + visible + ", removed=" + removed);
+ "type", windowType) + "visible=" + visible + ", removed=" + removed
+ + ", displayId=" + displayId);
}
mH.post(() -> mImeTargetChangeListener.onImeTargetOverlayVisibilityChanged(token,
- windowType, visible, removed));
+ windowType, visible, removed, displayId));
}
}
void dispatchImeInputTargetVisibilityChanged(@NonNull IBinder token, boolean visible,
- boolean removed) {
+ boolean removed, int displayId) {
if (mImeTargetChangeListener != null) {
if (DEBUG_INPUT_METHOD) {
Slog.d(TAG, "onImeInputTargetVisibilityChanged, win=" + mWindowMap.get(token)
- + "visible=" + visible + ", removed=" + removed);
+ + "visible=" + visible + ", removed=" + removed
+ + ", displayId" + displayId);
}
mH.post(() -> mImeTargetChangeListener.onImeInputTargetVisibilityChanged(token,
- visible, removed));
+ visible, removed, displayId));
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 153d41b..a61925f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2359,11 +2359,11 @@
}
super.removeImmediately();
+ final DisplayContent dc = getDisplayContent();
if (isImeOverlayLayeringTarget()) {
mWmService.dispatchImeTargetOverlayVisibilityChanged(mClient.asBinder(), mAttrs.type,
- false /* visible */, true /* removed */);
+ false /* visible */, true /* removed */, dc.getDisplayId());
}
- final DisplayContent dc = getDisplayContent();
if (isImeLayeringTarget()) {
// Remove the attached IME screenshot surface.
dc.removeImeSurfaceByTarget(this);
@@ -2374,7 +2374,7 @@
}
if (dc.getImeInputTarget() == this && !inRelaunchingActivity()) {
mWmService.dispatchImeInputTargetVisibilityChanged(mClient.asBinder(),
- false /* visible */, true /* removed */);
+ false /* visible */, true /* removed */, dc.getDisplayId());
dc.updateImeInputAndControlTarget(null);
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
index dd3b33e..4cd3157 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
@@ -302,12 +302,14 @@
final IBinder testImeInputTarget = new Binder();
// Simulate a test IME input target was visible.
- mListener.onImeInputTargetVisibilityChanged(testImeInputTarget, true, false);
+ mListener.onImeInputTargetVisibilityChanged(testImeInputTarget, true, false,
+ DEFAULT_DISPLAY);
// Simulate a test IME layering target overlay fully occluded the IME input target.
mListener.onImeTargetOverlayVisibilityChanged(testImeTargetOverlay,
- TYPE_APPLICATION_OVERLAY, true, false);
- mListener.onImeInputTargetVisibilityChanged(testImeInputTarget, false, false);
+ TYPE_APPLICATION_OVERLAY, true, false, DEFAULT_DISPLAY);
+ mListener.onImeInputTargetVisibilityChanged(testImeInputTarget, false, false,
+ DEFAULT_DISPLAY);
final ArgumentCaptor<IBinder> targetCaptor = ArgumentCaptor.forClass(IBinder.class);
final ArgumentCaptor<ImeVisibilityResult> resultCaptor = ArgumentCaptor.forClass(
ImeVisibilityResult.class);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index 7b71f85..1426d5d 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -48,6 +48,7 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.IntentFilter;
+import android.content.res.Resources;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
@@ -67,6 +68,7 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.R;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.LocalServices;
@@ -118,6 +120,7 @@
final FullScreenMagnificationController.ControllerContext mMockControllerCtx =
mock(FullScreenMagnificationController.ControllerContext.class);
final Context mMockContext = mock(Context.class);
+ final Resources mMockResources = mock(Resources.class);
final AccessibilityTraceManager mMockTraceManager = mock(AccessibilityTraceManager.class);
final WindowManagerInternal mMockWindowManager = mock(WindowManagerInternal.class);
private final MagnificationAnimationCallback mAnimationCallback = mock(
@@ -162,6 +165,7 @@
mResolver = new MockContentResolver();
mResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
when(mMockContext.getContentResolver()).thenReturn(mResolver);
+ when(mMockContext.getResources()).thenReturn(mMockResources);
mOriginalMagnificationPersistedScale = Settings.Secure.getFloatForUser(mResolver,
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 2.0f,
CURRENT_USER_ID);
@@ -928,7 +932,8 @@
/* displayId= */ i,
/* isMagnifierActivated= */ true,
/* isAlwaysOnEnabled= */ false,
- /* expectedActivated= */ false);
+ /* expectedActivated= */ false,
+ /* expectedMagnified= */ false);
resetMockWindowManager();
}
}
@@ -940,7 +945,24 @@
/* displayId= */ i,
/* isMagnifierActivated= */ true,
/* isAlwaysOnEnabled= */ true,
- /* expectedActivated= */ true);
+ /* expectedActivated= */ true,
+ /* expectedMagnified= */ false);
+ resetMockWindowManager();
+ }
+ }
+
+ @Test
+ public void testUserContextChange_magnifierActivatedAndKeepMagnifiedEnabled_stayActivated() {
+ when(mMockResources.getBoolean(
+ R.bool.config_magnification_keep_zoom_level_when_context_changed))
+ .thenReturn(true);
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ contextChange_expectedValues(
+ /* displayId= */ i,
+ /* isMagnifierActivated= */ true,
+ /* isAlwaysOnEnabled= */ true,
+ /* expectedActivated= */ true,
+ /* expectedMagnified= */ true);
resetMockWindowManager();
}
}
@@ -952,7 +974,8 @@
/* displayId= */ i,
/* isMagnifierActivated= */ false,
/* isAlwaysOnEnabled= */ false,
- /* expectedActivated= */ false);
+ /* expectedActivated= */ false,
+ /* expectedMagnified= */ false);
resetMockWindowManager();
}
}
@@ -964,14 +987,15 @@
/* displayId= */ i,
/* isMagnifierActivated= */ false,
/* isAlwaysOnEnabled= */ true,
- /* expectedActivated= */ false);
+ /* expectedActivated= */ false,
+ /* expectedMagnified= */ false);
resetMockWindowManager();
}
}
private void contextChange_expectedValues(
int displayId, boolean isMagnifierActivated, boolean isAlwaysOnEnabled,
- boolean expectedActivated) {
+ boolean expectedActivated, boolean expectedMagnified) {
mFullScreenMagnificationController.setAlwaysOnMagnificationEnabled(isAlwaysOnEnabled);
register(displayId);
MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
@@ -982,7 +1006,7 @@
callbacks.onUserContextChanged();
mMessageCapturingHandler.sendAllMessages();
checkActivatedAndMagnifying(
- /* activated= */ expectedActivated, /* magnifying= */ false, displayId);
+ /* activated= */ expectedActivated, expectedMagnified, displayId);
if (expectedActivated) {
verify(mMockThumbnail, times(2)).setThumbnailBounds(
@@ -1526,8 +1550,8 @@
private void checkActivatedAndMagnifying(boolean activated, boolean magnifying, int displayId) {
final boolean isActivated = mFullScreenMagnificationController.isActivated(displayId);
final boolean isMagnifying = mFullScreenMagnificationController.getScale(displayId) > 1.0f;
- assertTrue(isActivated == activated);
- assertTrue(isMagnifying == magnifying);
+ assertEquals(isActivated, activated);
+ assertEquals(isMagnifying, magnifying);
}
private MagnificationCallbacks getMagnificationCallbacks(int displayId) {
diff --git a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
index eed4b0b..9b92ff4 100644
--- a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
@@ -194,6 +194,26 @@
}
@Test
+ public void stemLongKey_appHasOverridePermission_consumedByApp_triggerStatusBarToStartAssist() {
+ overrideBehavior(
+ STEM_PRIMARY_BUTTON_LONG_PRESS,
+ LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT);
+ setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+ mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false);
+ mPhoneWindowManager.setupAssistForLaunch();
+ mPhoneWindowManager.overrideSearchManager(null);
+ mPhoneWindowManager.overrideStatusBarManagerInternal();
+ mPhoneWindowManager.overrideIsUserSetupComplete(true);
+ mPhoneWindowManager.overrideFocusedWindowButtonOverridePermission(true);
+
+ setDispatchedKeyHandler(keyEvent -> true);
+
+ sendKey(KEYCODE_STEM_PRIMARY, /* longPress= */ true);
+
+ mPhoneWindowManager.assertStatusBarStartAssist();
+ }
+
+ @Test
public void stemDoubleKey_EarlyShortPress_AllAppsThenSwitchToMostRecent()
throws RemoteException {
overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index b46189c..11df331 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -1351,6 +1351,7 @@
assertThat(listener.mImeTargetToken).isEqualTo(imeTarget.mClient.asBinder());
assertThat(listener.mIsRemoved).isFalse();
assertThat(listener.mIsVisibleForImeInputTarget).isTrue();
+ assertThat(listener.mDisplayId).isEqualTo(mDisplayContent.getDisplayId());
imeTarget.mActivityRecord.setVisibleRequested(false);
waitHandlerIdle(mWm.mH);
@@ -1358,11 +1359,13 @@
assertThat(listener.mImeTargetToken).isEqualTo(imeTarget.mClient.asBinder());
assertThat(listener.mIsRemoved).isFalse();
assertThat(listener.mIsVisibleForImeInputTarget).isFalse();
+ assertThat(listener.mDisplayId).isEqualTo(mDisplayContent.getDisplayId());
imeTarget.removeImmediately();
assertThat(listener.mImeTargetToken).isEqualTo(imeTarget.mClient.asBinder());
assertThat(listener.mIsRemoved).isTrue();
assertThat(listener.mIsVisibleForImeInputTarget).isFalse();
+ assertThat(listener.mDisplayId).isEqualTo(mDisplayContent.getDisplayId());
}
@SetupWindows(addWindows = {W_INPUT_METHOD})
@@ -1402,6 +1405,7 @@
assertThat(listener.mImeTargetToken).isEqualTo(client.asBinder());
assertThat(listener.mIsRemoved).isFalse();
assertThat(listener.mIsVisibleForImeTargetOverlay).isTrue();
+ assertThat(listener.mDisplayId).isEqualTo(mDisplayContent.getDisplayId());
// Scenario 2: test relayoutWindow to let the Ime layering target overlay window invisible.
mWm.relayoutWindow(session, client, params, 100, 200, View.GONE, 0, 0, 0,
@@ -1412,6 +1416,7 @@
assertThat(listener.mImeTargetToken).isEqualTo(client.asBinder());
assertThat(listener.mIsRemoved).isFalse();
assertThat(listener.mIsVisibleForImeTargetOverlay).isFalse();
+ assertThat(listener.mDisplayId).isEqualTo(mDisplayContent.getDisplayId());
// Scenario 3: test removeWindow to remove the Ime layering target overlay window.
mWm.removeClientToken(session, client.asBinder());
@@ -1420,6 +1425,7 @@
assertThat(listener.mImeTargetToken).isEqualTo(client.asBinder());
assertThat(listener.mIsRemoved).isTrue();
assertThat(listener.mIsVisibleForImeTargetOverlay).isFalse();
+ assertThat(listener.mDisplayId).isEqualTo(mDisplayContent.getDisplayId());
}
@Test
@@ -1468,22 +1474,25 @@
private boolean mIsRemoved;
private boolean mIsVisibleForImeTargetOverlay;
private boolean mIsVisibleForImeInputTarget;
+ private int mDisplayId;
@Override
public void onImeTargetOverlayVisibilityChanged(IBinder overlayWindowToken,
@WindowManager.LayoutParams.WindowType int windowType, boolean visible,
- boolean removed) {
+ boolean removed, int displayId) {
mImeTargetToken = overlayWindowToken;
mIsVisibleForImeTargetOverlay = visible;
mIsRemoved = removed;
+ mDisplayId = displayId;
}
@Override
public void onImeInputTargetVisibilityChanged(IBinder imeInputTarget,
- boolean visibleRequested, boolean removed) {
+ boolean visibleRequested, boolean removed, int displayId) {
mImeTargetToken = imeInputTarget;
mIsVisibleForImeInputTarget = visibleRequested;
mIsRemoved = removed;
+ mDisplayId = displayId;
}
}
}
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 1404413..6c1e1a4 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -80,9 +80,9 @@
import android.provider.Settings;
import android.service.usb.UsbDeviceManagerProto;
import android.service.usb.UsbHandlerProto;
+import android.text.TextUtils;
import android.util.Pair;
import android.util.Slog;
-import android.text.TextUtils;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -880,7 +880,7 @@
}
}
- private void notifyAccessoryModeExit(int operationId) {
+ protected void notifyAccessoryModeExit(int operationId) {
// make sure accessory mode is off
// and restore default functions
Slog.d(TAG, "exited USB accessory mode");
@@ -2313,8 +2313,13 @@
*/
operationId = sUsbOperationCount.incrementAndGet();
if (msg.arg1 != 1) {
- // Set this since default function may be selected from Developer options
- setEnabledFunctions(mScreenUnlockedFunctions, false, operationId);
+ if (mCurrentFunctions == UsbManager.FUNCTION_ACCESSORY) {
+ notifyAccessoryModeExit(operationId);
+ } else {
+ // Set this since default function may be selected from Developer
+ // options
+ setEnabledFunctions(mScreenUnlockedFunctions, false, operationId);
+ }
}
break;
case MSG_GADGET_HAL_REGISTERED:
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index c6959ae..b9a001d 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9604,9 +9604,8 @@
* Defines the rules for data setup retry.
*
* The syntax of the retry rule:
- * 1. Retry based on {@link NetworkCapabilities}. Note that only APN-type network capabilities
- * are supported. If the capabilities are not specified, then the retry rule only applies
- * to the current failed APN used in setup data call request.
+ * 1. Retry based on {@link NetworkCapabilities}. If the capabilities are not specified, then
+ * the retry rule only applies to the current failed APN used in setup data call request.
* "capabilities=[netCaps1|netCaps2|...], [retry_interval=n1|n2|n3|n4...], [maximum_retries=n]"
*
* 2. Retry based on {@link DataFailCause}