diff options
107 files changed, 3770 insertions, 912 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 458d1dfeadb8..2ce3221e56cf 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -102,6 +102,7 @@ aconfig_declarations_group { "com.android.media.flags.projection-aconfig-java", "com.android.net.http.flags-aconfig-exported-java", "com.android.net.thread.platform.flags-aconfig-java", + "com.android.permission.flags-aconfig-java-export", "com.android.ranging.flags.ranging-aconfig-java-export", "com.android.server.contextualsearch.flags-java", "com.android.server.flags.services-aconfig-java", @@ -16,6 +16,7 @@ omakoto@google.com #{LAST_RESORT_SUGGESTION} roosa@google.com #{LAST_RESORT_SUGGESTION} smoreland@google.com #{LAST_RESORT_SUGGESTION} yamasani@google.com #{LAST_RESORT_SUGGESTION} +timmurray@google.com #{LAST_RESORT_SUGGESTION} # API changes are already covered by API-Review+1 (http://mdb/android-api-council) # via https://android.git.corp.google.com/All-Projects/+/refs/meta/config/rules.pl. diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 4aa74621bd62..875b9098843f 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -2831,9 +2831,11 @@ public final class Parcel { } } - private void resetSqaushingState() { + private void resetSquashingState() { if (mAllowSquashing) { - Slog.wtf(TAG, "allowSquashing wasn't restored."); + String error = "allowSquashing wasn't restored."; + Slog.wtf(TAG, error); + throw new BadParcelableException(error); } mWrittenSquashableParcelables = null; mReadSquashableParcelables = null; @@ -2950,9 +2952,11 @@ public final class Parcel { for (int i = 0; i < mReadSquashableParcelables.size(); i++) { sb.append(mReadSquashableParcelables.keyAt(i)).append(' '); } - Slog.wtfStack(TAG, "Map doesn't contain offset " + String error = "Map doesn't contain offset " + firstAbsolutePos - + " : contains=" + sb.toString()); + + " : contains=" + sb.toString(); + Slog.wtfStack(TAG, error); + throw new BadParcelableException(error); } return (T) p; } @@ -5505,7 +5509,7 @@ public final class Parcel { private void freeBuffer() { mFlags = 0; - resetSqaushingState(); + resetSquashingState(); if (mOwnsNativeParcelObject) { nativeFreeBuffer(mNativePtr); } @@ -5513,7 +5517,7 @@ public final class Parcel { } private void destroy() { - resetSqaushingState(); + resetSquashingState(); if (mNativePtr != 0) { if (mOwnsNativeParcelObject) { nativeDestroy(mNativePtr); diff --git a/core/java/android/os/PerfettoTrace.java b/core/java/android/os/PerfettoTrace.java index 164561acac32..e3f251e34b45 100644 --- a/core/java/android/os/PerfettoTrace.java +++ b/core/java/android/os/PerfettoTrace.java @@ -22,7 +22,6 @@ import dalvik.annotation.optimization.FastNative; import libcore.util.NativeAllocationRegistry; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; /** * Writes trace events to the perfetto trace buffer. These trace events can be @@ -72,7 +71,7 @@ public final class PerfettoTrace { * @param name The category name. */ public Category(String name) { - this(name, null, null); + this(name, "", ""); } /** @@ -82,7 +81,7 @@ public final class PerfettoTrace { * @param tag An atrace tag name that this category maps to. */ public Category(String name, String tag) { - this(name, tag, null); + this(name, tag, ""); } /** @@ -155,9 +154,6 @@ public final class PerfettoTrace { } } - @FastNative - private static native void native_event(int type, long tag, String name, long ptr); - @CriticalNative private static native long native_get_process_track_uuid(); @@ -170,176 +166,98 @@ public final class PerfettoTrace { /** * Writes a trace message to indicate a given section of code was invoked. * - * @param category The perfetto category pointer. + * @param category The perfetto category. * @param eventName The event name to appear in the trace. - * @param extra The extra arguments. */ - public static void instant(Category category, String eventName, PerfettoTrackEventExtra extra) { + public static PerfettoTrackEventExtra.Builder instant(Category category, String eventName) { if (!category.isEnabled()) { - return; + return PerfettoTrackEventExtra.noOpBuilder(); } - native_event(PERFETTO_TE_TYPE_INSTANT, category.getPtr(), eventName, extra.getPtr()); - extra.reset(); - } - - /** - * Writes a trace message to indicate a given section of code was invoked. - * - * @param category The perfetto category. - * @param eventName The event name to appear in the trace. - * @param extraConfig Consumer for the extra arguments. - */ - public static void instant(Category category, String eventName, - Consumer<PerfettoTrackEventExtra.Builder> extraConfig) { - PerfettoTrackEventExtra.Builder extra = PerfettoTrackEventExtra.builder(); - extraConfig.accept(extra); - instant(category, eventName, extra.build()); - } - - /** - * Writes a trace message to indicate a given section of code was invoked. - * - * @param category The perfetto category. - * @param eventName The event name to appear in the trace. - */ - public static void instant(Category category, String eventName) { - instant(category, eventName, PerfettoTrackEventExtra.builder().build()); + return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_INSTANT, category) + .setEventName(eventName); } /** * Writes a trace message to indicate the start of a given section of code. * - * @param category The perfetto category pointer. + * @param category The perfetto category. * @param eventName The event name to appear in the trace. - * @param extra The extra arguments. */ - public static void begin(Category category, String eventName, PerfettoTrackEventExtra extra) { + public static PerfettoTrackEventExtra.Builder begin(Category category, String eventName) { if (!category.isEnabled()) { - return; + return PerfettoTrackEventExtra.noOpBuilder(); } - native_event(PERFETTO_TE_TYPE_SLICE_BEGIN, category.getPtr(), eventName, extra.getPtr()); - extra.reset(); - } - - /** - * Writes a trace message to indicate the start of a given section of code. - * - * @param category The perfetto category pointer. - * @param eventName The event name to appear in the trace. - * @param extraConfig Consumer for the extra arguments. - */ - public static void begin(Category category, String eventName, - Consumer<PerfettoTrackEventExtra.Builder> extraConfig) { - PerfettoTrackEventExtra.Builder extra = PerfettoTrackEventExtra.builder(); - extraConfig.accept(extra); - begin(category, eventName, extra.build()); - } - - /** - * Writes a trace message to indicate the start of a given section of code. - * - * @param category The perfetto category pointer. - * @param eventName The event name to appear in the trace. - */ - public static void begin(Category category, String eventName) { - begin(category, eventName, PerfettoTrackEventExtra.builder().build()); + return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_SLICE_BEGIN, category) + .setEventName(eventName); } /** * Writes a trace message to indicate the end of a given section of code. * - * @param category The perfetto category pointer. - * @param extra The extra arguments. + * @param category The perfetto category. */ - public static void end(Category category, PerfettoTrackEventExtra extra) { + public static PerfettoTrackEventExtra.Builder end(Category category) { if (!category.isEnabled()) { - return; + return PerfettoTrackEventExtra.noOpBuilder(); } - native_event(PERFETTO_TE_TYPE_SLICE_END, category.getPtr(), "", extra.getPtr()); - extra.reset(); - } - - /** - * Writes a trace message to indicate the end of a given section of code. - * - * @param category The perfetto category pointer. - * @param extraConfig Consumer for the extra arguments. - */ - public static void end(Category category, - Consumer<PerfettoTrackEventExtra.Builder> extraConfig) { - PerfettoTrackEventExtra.Builder extra = PerfettoTrackEventExtra.builder(); - extraConfig.accept(extra); - end(category, extra.build()); - } - - /** - * Writes a trace message to indicate the end of a given section of code. - * - * @param category The perfetto category pointer. - */ - public static void end(Category category) { - end(category, PerfettoTrackEventExtra.builder().build()); + return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_SLICE_END, category); } /** * Writes a trace message to indicate the value of a given section of code. * - * @param category The perfetto category pointer. - * @param extra The extra arguments. + * @param category The perfetto category. + * @param value The value of the counter. */ - public static void counter(Category category, PerfettoTrackEventExtra extra) { + public static PerfettoTrackEventExtra.Builder counter(Category category, long value) { if (!category.isEnabled()) { - return; + return PerfettoTrackEventExtra.noOpBuilder(); } - native_event(PERFETTO_TE_TYPE_COUNTER, category.getPtr(), "", extra.getPtr()); - extra.reset(); + return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_COUNTER, category) + .setCounter(value); } /** * Writes a trace message to indicate the value of a given section of code. * - * @param category The perfetto category pointer. - * @param extraConfig Consumer for the extra arguments. + * @param category The perfetto category. + * @param value The value of the counter. + * @param trackName The trackName for the event. */ - public static void counter(Category category, - Consumer<PerfettoTrackEventExtra.Builder> extraConfig) { - PerfettoTrackEventExtra.Builder extra = PerfettoTrackEventExtra.builder(); - extraConfig.accept(extra); - counter(category, extra.build()); + public static PerfettoTrackEventExtra.Builder counter( + Category category, long value, String trackName) { + return counter(category, value).usingProcessCounterTrack(trackName); } /** * Writes a trace message to indicate the value of a given section of code. * - * @param category The perfetto category pointer. - * @param trackName The trackName for the event. + * @param category The perfetto category. * @param value The value of the counter. */ - public static void counter(Category category, String trackName, long value) { - PerfettoTrackEventExtra extra = PerfettoTrackEventExtra.builder() - .usingCounterTrack(trackName, PerfettoTrace.getProcessTrackUuid()) - .setCounter(value) - .build(); - counter(category, extra); + public static PerfettoTrackEventExtra.Builder counter(Category category, double value) { + if (!category.isEnabled()) { + return PerfettoTrackEventExtra.noOpBuilder(); + } + + return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_COUNTER, category) + .setCounter(value); } /** * Writes a trace message to indicate the value of a given section of code. * - * @param category The perfetto category pointer. - * @param trackName The trackName for the event. + * @param category The perfetto category. * @param value The value of the counter. + * @param trackName The trackName for the event. */ - public static void counter(Category category, String trackName, double value) { - PerfettoTrackEventExtra extra = PerfettoTrackEventExtra.builder() - .usingCounterTrack(trackName, PerfettoTrace.getProcessTrackUuid()) - .setCounter(value) - .build(); - counter(category, extra); + public static PerfettoTrackEventExtra.Builder counter( + Category category, double value, String trackName) { + return counter(category, value).usingProcessCounterTrack(trackName); } /** @@ -360,7 +278,7 @@ public final class PerfettoTrace { * Returns the process track uuid that can be used as a parent track uuid. */ public static long getProcessTrackUuid() { - if (IS_FLAG_ENABLED) { + if (!IS_FLAG_ENABLED) { return 0; } return native_get_process_track_uuid(); @@ -370,7 +288,7 @@ public final class PerfettoTrace { * Given a thread tid, returns the thread track uuid that can be used as a parent track uuid. */ public static long getThreadTrackUuid(long tid) { - if (IS_FLAG_ENABLED) { + if (!IS_FLAG_ENABLED) { return 0; } return native_get_thread_track_uuid(tid); @@ -380,7 +298,7 @@ public final class PerfettoTrace { * Activates a trigger by name {@code triggerName} with expiry in {@code ttlMs}. */ public static void activateTrigger(String triggerName, int ttlMs) { - if (IS_FLAG_ENABLED) { + if (!IS_FLAG_ENABLED) { return; } native_activate_trigger(triggerName, ttlMs); diff --git a/core/java/android/os/PerfettoTrackEventExtra.java b/core/java/android/os/PerfettoTrackEventExtra.java index a219b3b5678b..e034fb3726e3 100644 --- a/core/java/android/os/PerfettoTrackEventExtra.java +++ b/core/java/android/os/PerfettoTrackEventExtra.java @@ -31,6 +31,7 @@ import java.util.function.Supplier; */ public final class PerfettoTrackEventExtra { private static final int DEFAULT_EXTRA_CACHE_SIZE = 5; + private static final Builder NO_OP_BUILDER = new NoOpBuilder(); private static final ThreadLocal<PerfettoTrackEventExtra> sTrackEventExtra = new ThreadLocal<PerfettoTrackEventExtra>() { @Override @@ -40,7 +41,6 @@ public final class PerfettoTrackEventExtra { }; private static final AtomicLong sNamedTrackId = new AtomicLong(); - private boolean mIsInUse; private CounterInt64 mCounterInt64; private CounterDouble mCounterDouble; private Proto mProto; @@ -123,15 +123,299 @@ public final class PerfettoTrackEventExtra { } } + public interface Builder { + /** + * Emits the track event. + */ + void emit(); + + /** + * Initialize the builder for a new trace event. + */ + Builder init(int traceType, PerfettoTrace.Category category); + + /** + * Sets the event name for the track event. + * + * @param eventName can contain a format string specifier, in which case, the + * {@code args} are the format arguments. If no {@code args} are provided, + * the {@code eventName} should be the string itself. + * @param args format arguments if {@code eventName} is specified. + */ + Builder setEventName(String eventName, Object... args); + + /** + * Adds a debug arg with key {@code name} and value {@code val}. + */ + Builder addArg(String name, long val); + + /** + * Adds a debug arg with key {@code name} and value {@code val}. + */ + Builder addArg(String name, boolean val); + + /** + * Adds a debug arg with key {@code name} and value {@code val}. + */ + Builder addArg(String name, double val); + + /** + * Adds a debug arg with key {@code name} and value {@code val}. + * + * @param val can contain a format string specifier, in which case, the + * {@code args} are the format arguments. If no {@code args} are provided, + * the {@code val} should be the string itself. + * @param args format arguments if {@code val} is specified. + */ + Builder addArg(String name, String val, Object... args); + + /** + * Adds a flow with {@code id}. + */ + Builder addFlow(int id); + + /** + * Adds a terminating flow with {@code id}. + */ + Builder addTerminatingFlow(int id); + + /** + * Adds the events to a named track instead of the thread track where the + * event occurred. + * + * @param name can contain a format string specifier, in which case, the + * {@code args} are the format arguments. If no {@code args} are provided, + * the {@code name} should be the string itself. + * @param args format arguments if {@code name} is specified. + */ + Builder usingNamedTrack(long parentUuid, String name, Object... args); + + /** + * Adds the events to a process scoped named track instead of the thread track where the + * event occurred. + * + * @param name can contain a format string specifier, in which case, the + * {@code args} are the format arguments. If no {@code args} are provided, + * the {@code name} should be the string itself. + * @param args format arguments if {@code name} is specified. + */ + Builder usingProcessNamedTrack(String name, Object... args); + + /** + * Adds the events to a thread scoped named track instead of the thread track where the + * event occurred. + * + * @param name can contain a format string specifier, in which case, the + * {@code args} are the format arguments. If no {@code args} are provided, + * the {@code name} should be the string itself. + * @param args format arguments if {@code name} is specified. + */ + Builder usingThreadNamedTrack(long tid, String name, Object... args); + + /** + * Adds the events to a counter track instead. This is required for + * setting counter values. + * + * @param name can contain a format string specifier, in which case, the + * {@code args} are the format arguments. If no {@code args} are provided, + * the {@code name} should be the string itself. + * @param args format arguments if {@code name} is specified. + */ + Builder usingCounterTrack(long parentUuid, String name, Object... args); + + /** + * Adds the events to a process scoped counter track instead. This is required for + * setting counter values. + * + * @param name can contain a format string specifier, in which case, the + * {@code args} are the format arguments. If no {@code args} are provided, + * the {@code name} should be the string itself. + * @param args format arguments if {@code eventName} is specified. + */ + Builder usingProcessCounterTrack(String name, Object... args); + + /** + * Adds the events to a thread scoped counter track instead. This is required for + * setting counter values. + * + * @param name can contain a format string specifier, in which case, the + * {@code args} are the format arguments. If no {@code args} are provided, + * the {@code name} should be the string itself. + * @param args format arguments if {@code name} is specified. + */ + Builder usingThreadCounterTrack(long tid, String name, Object... args); + + /** + * Sets a long counter value on the event. + * + */ + Builder setCounter(long val); + + /** + * Sets a double counter value on the event. + * + */ + Builder setCounter(double val); + + /** + * Adds a proto field with field id {@code id} and value {@code val}. + */ + Builder addField(long id, long val); + + /** + * Adds a proto field with field id {@code id} and value {@code val}. + */ + Builder addField(long id, double val); + + /** + * Adds a proto field with field id {@code id} and value {@code val}. + * + * @param val can contain a format string specifier, in which case, the + * {@code args} are the format arguments. If no {@code args} are provided, + * the {@code val} should be the string itself. + * @param args format arguments if {@code val} is specified. + */ + Builder addField(long id, String val, Object... args); + + /** + * Begins a proto field with field + * Fields can be added from this point and there must be a corresponding + * {@link endProto}. + * + * The proto field is a singleton and all proto fields get added inside the + * one {@link beginProto} and {@link endProto} within the {@link Builder}. + */ + Builder beginProto(); + + /** + * Ends a proto field. + */ + Builder endProto(); + + /** + * Begins a nested proto field with field id {@code id}. + * Fields can be added from this point and there must be a corresponding + * {@link endNested}. + */ + Builder beginNested(long id); + + /** + * Ends a nested proto field. + */ + Builder endNested(); + } + + public static final class NoOpBuilder implements Builder { + @Override + public void emit() {} + @Override + public Builder init(int traceType, PerfettoTrace.Category category) { + return this; + } + @Override + public Builder setEventName(String eventName, Object... args) { + return this; + } + @Override + public Builder addArg(String name, long val) { + return this; + } + @Override + public Builder addArg(String name, boolean val) { + return this; + } + @Override + public Builder addArg(String name, double val) { + return this; + } + @Override + public Builder addArg(String name, String val, Object... args) { + return this; + } + @Override + public Builder addFlow(int id) { + return this; + } + @Override + public Builder addTerminatingFlow(int id) { + return this; + } + @Override + public Builder usingNamedTrack(long parentUuid, String name, Object... args) { + return this; + } + @Override + public Builder usingProcessNamedTrack(String name, Object... args) { + return this; + } + @Override + public Builder usingThreadNamedTrack(long tid, String name, Object... args) { + return this; + } + @Override + public Builder usingCounterTrack(long parentUuid, String name, Object... args) { + return this; + } + @Override + public Builder usingProcessCounterTrack(String name, Object... args) { + return this; + } + @Override + public Builder usingThreadCounterTrack(long tid, String name, Object... args) { + return this; + } + @Override + public Builder setCounter(long val) { + return this; + } + @Override + public Builder setCounter(double val) { + return this; + } + @Override + public Builder addField(long id, long val) { + return this; + } + @Override + public Builder addField(long id, double val) { + return this; + } + @Override + public Builder addField(long id, String val, Object... args) { + return this; + } + @Override + public Builder beginProto() { + return this; + } + @Override + public Builder endProto() { + return this; + } + @Override + public Builder beginNested(long id) { + return this; + } + @Override + public Builder endNested() { + return this; + } + } + /** * Builder for Perfetto track event extras. */ - public static final class Builder { + public static final class BuilderImpl implements Builder { // For performance reasons, we hold a reference to mExtra as a holder for // perfetto pointers being added. This way, we avoid an additional list to hold // the pointers in Java and we can pass them down directly to native code. private final PerfettoTrackEventExtra mExtra; + + private int mTraceType; + private PerfettoTrace.Category mCategory; + private String mEventName; private boolean mIsBuilt; + private Builder mParent; private FieldContainer mCurrentContainer; @@ -151,16 +435,10 @@ public final class PerfettoTrackEventExtra { private final Pool<FieldString> mFieldStringCache; private final Pool<FieldNested> mFieldNestedCache; private final Pool<Flow> mFlowCache; - private final Pool<Builder> mBuilderCache; + private final Pool<BuilderImpl> mBuilderCache; - private Builder() { - this(sTrackEventExtra.get(), null, null); - } - - private Builder(PerfettoTrackEventExtra extra, Builder parent, FieldContainer container) { - mExtra = extra; - mParent = parent; - mCurrentContainer = container; + private BuilderImpl() { + mExtra = sTrackEventExtra.get(); mNamedTrackCache = mExtra.mNamedTrackCache; mCounterTrackCache = mExtra.mCounterTrackCache; @@ -180,25 +458,39 @@ public final class PerfettoTrackEventExtra { mProto = mExtra.getProto(); } - /** - * Builds the track event extra. - */ - public PerfettoTrackEventExtra build() { + @Override + public void emit() { checkParent(); mIsBuilt = true; + native_emit(mTraceType, mCategory.getPtr(), mEventName, mExtra.getPtr()); + // Reset after emitting to free any the extras used to trace the event. + mExtra.reset(); + } + + @Override + public Builder init(int traceType, PerfettoTrace.Category category) { + mTraceType = traceType; + mCategory = category; + mEventName = ""; mFieldInt64Cache.reset(); mFieldDoubleCache.reset(); mFieldStringCache.reset(); mFieldNestedCache.reset(); mBuilderCache.reset(); - return mExtra; + mExtra.reset(); + // Reset after on init in case the thread created builders without calling emit + return initInternal(this, null); } - /** - * Adds a debug arg with key {@code name} and value {@code val}. - */ + @Override + public Builder setEventName(String eventName, Object... args) { + mEventName = toString(eventName, args); + return this; + } + + @Override public Builder addArg(String name, long val) { checkParent(); ArgInt64 arg = mArgInt64Cache.get(name.hashCode()); @@ -211,9 +503,7 @@ public final class PerfettoTrackEventExtra { return this; } - /** - * Adds a debug arg with key {@code name} and value {@code val}. - */ + @Override public Builder addArg(String name, boolean val) { checkParent(); ArgBool arg = mArgBoolCache.get(name.hashCode()); @@ -226,9 +516,7 @@ public final class PerfettoTrackEventExtra { return this; } - /** - * Adds a debug arg with key {@code name} and value {@code val}. - */ + @Override public Builder addArg(String name, double val) { checkParent(); ArgDouble arg = mArgDoubleCache.get(name.hashCode()); @@ -241,24 +529,20 @@ public final class PerfettoTrackEventExtra { return this; } - /** - * Adds a debug arg with key {@code name} and value {@code val}. - */ - public Builder addArg(String name, String val) { + @Override + public Builder addArg(String name, String val, Object... args) { checkParent(); ArgString arg = mArgStringCache.get(name.hashCode()); if (arg == null || !arg.getName().equals(name)) { arg = new ArgString(name); mArgStringCache.put(name.hashCode(), arg); } - arg.setValue(val); + arg.setValue(toString(val, args)); mExtra.addPerfettoPointer(arg); return this; } - /** - * Adds a flow with {@code id}. - */ + @Override public Builder addFlow(int id) { checkParent(); Flow flow = mFlowCache.get(Flow::new); @@ -267,9 +551,7 @@ public final class PerfettoTrackEventExtra { return this; } - /** - * Adds a terminating flow with {@code id}. - */ + @Override public Builder addTerminatingFlow(int id) { checkParent(); Flow flow = mFlowCache.get(Flow::new); @@ -278,12 +560,11 @@ public final class PerfettoTrackEventExtra { return this; } - /** - * Adds the events to a named track instead of the thread track where the - * event occurred. - */ - public Builder usingNamedTrack(String name, long parentUuid) { + @Override + public Builder usingNamedTrack(long parentUuid, String name, Object... args) { checkParent(); + name = toString(name, args); + NamedTrack track = mNamedTrackCache.get(name.hashCode()); if (track == null || !track.getName().equals(name)) { track = new NamedTrack(name, parentUuid); @@ -293,13 +574,21 @@ public final class PerfettoTrackEventExtra { return this; } - /** - * Adds the events to a counter track instead. This is required for - * setting counter values. - * - */ - public Builder usingCounterTrack(String name, long parentUuid) { + @Override + public Builder usingProcessNamedTrack(String name, Object... args) { + return usingNamedTrack(PerfettoTrace.getProcessTrackUuid(), name, args); + } + + @Override + public Builder usingThreadNamedTrack(long tid, String name, Object... args) { + return usingNamedTrack(PerfettoTrace.getThreadTrackUuid(tid), name, args); + } + + @Override + public Builder usingCounterTrack(long parentUuid, String name, Object... args) { checkParent(); + name = toString(name, args); + CounterTrack track = mCounterTrackCache.get(name.hashCode()); if (track == null || !track.getName().equals(name)) { track = new CounterTrack(name, parentUuid); @@ -309,10 +598,17 @@ public final class PerfettoTrackEventExtra { return this; } - /** - * Sets a long counter value on the event. - * - */ + @Override + public Builder usingProcessCounterTrack(String name, Object... args) { + return usingCounterTrack(PerfettoTrace.getProcessTrackUuid(), name, args); + } + + @Override + public Builder usingThreadCounterTrack(long tid, String name, Object... args) { + return usingCounterTrack(PerfettoTrace.getThreadTrackUuid(tid), name, args); + } + + @Override public Builder setCounter(long val) { checkParent(); mCounterInt64.setValue(val); @@ -320,10 +616,7 @@ public final class PerfettoTrackEventExtra { return this; } - /** - * Sets a double counter value on the event. - * - */ + @Override public Builder setCounter(double val) { checkParent(); mCounterDouble.setValue(val); @@ -331,9 +624,7 @@ public final class PerfettoTrackEventExtra { return this; } - /** - * Adds a proto field with field id {@code id} and value {@code val}. - */ + @Override public Builder addField(long id, long val) { checkContainer(); FieldInt64 field = mFieldInt64Cache.get(FieldInt64::new); @@ -342,9 +633,7 @@ public final class PerfettoTrackEventExtra { return this; } - /** - * Adds a proto field with field id {@code id} and value {@code val}. - */ + @Override public Builder addField(long id, double val) { checkContainer(); FieldDouble field = mFieldDoubleCache.get(FieldDouble::new); @@ -353,35 +642,24 @@ public final class PerfettoTrackEventExtra { return this; } - /** - * Adds a proto field with field id {@code id} and value {@code val}. - */ - public Builder addField(long id, String val) { + @Override + public Builder addField(long id, String val, Object... args) { checkContainer(); FieldString field = mFieldStringCache.get(FieldString::new); - field.setValue(id, val); + field.setValue(id, toString(val, args)); mCurrentContainer.addField(field); return this; } - /** - * Begins a proto field with field - * Fields can be added from this point and there must be a corresponding - * {@link endProto}. - * - * The proto field is a singleton and all proto fields get added inside the - * one {@link beginProto} and {@link endProto} within the {@link Builder}. - */ + @Override public Builder beginProto() { checkParent(); mProto.clearFields(); mExtra.addPerfettoPointer(mProto); - return mBuilderCache.get(Builder::new).init(this, mProto); + return mBuilderCache.get(BuilderImpl::new).initInternal(this, mProto); } - /** - * Ends a proto field. - */ + @Override public Builder endProto() { if (mParent == null || mCurrentContainer == null) { throw new IllegalStateException("No proto to end"); @@ -389,22 +667,16 @@ public final class PerfettoTrackEventExtra { return mParent; } - /** - * Begins a nested proto field with field id {@code id}. - * Fields can be added from this point and there must be a corresponding - * {@link endNested}. - */ + @Override public Builder beginNested(long id) { checkContainer(); FieldNested field = mFieldNestedCache.get(FieldNested::new); field.setId(id); mCurrentContainer.addField(field); - return mBuilderCache.get(Builder::new).init(this, field); + return mBuilderCache.get(BuilderImpl::new).initInternal(this, field); } - /** - * Ends a nested proto field. - */ + @Override public Builder endNested() { if (mParent == null || mCurrentContainer == null) { throw new IllegalStateException("No nested field to end"); @@ -412,21 +684,15 @@ public final class PerfettoTrackEventExtra { return mParent; } - /** - * Initializes a {@link Builder}. - */ - public Builder init(Builder parent, FieldContainer container) { + private static String toString(String val, Object... args) { + return args == null || args.length == 0 ? val : String.format(val, args); + } + + private Builder initInternal(Builder parent, FieldContainer container) { mParent = parent; mCurrentContainer = container; mIsBuilt = false; - if (mParent == null) { - if (mExtra.mIsInUse) { - throw new IllegalStateException("Cannot create a new builder when another" - + " extra is in use"); - } - mExtra.mIsInUse = true; - } return this; } @@ -439,9 +705,8 @@ public final class PerfettoTrackEventExtra { private void checkParent() { checkState(); - if (mParent != null) { - throw new IllegalStateException( - "This builder has already been used. Create a new builder for another event."); + if (!this.equals(mParent)) { + throw new IllegalStateException("Operation not supported for proto"); } } @@ -458,7 +723,14 @@ public final class PerfettoTrackEventExtra { * Start a {@link Builder} to build a {@link PerfettoTrackEventExtra}. */ public static Builder builder() { - return sTrackEventExtra.get().mBuilderCache.get(Builder::new).init(null, null); + return sTrackEventExtra.get().mBuilderCache.get(BuilderImpl::new).initInternal(null, null); + } + + /** + * Returns a no-op {@link Builder}. Useful if a category is disabled. + */ + public static Builder noOpBuilder() { + return NO_OP_BUILDER; } private final RingBuffer<NamedTrack> mNamedTrackCache = @@ -476,7 +748,7 @@ public final class PerfettoTrackEventExtra { private final Pool<FieldString> mFieldStringCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE); private final Pool<FieldNested> mFieldNestedCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE); private final Pool<Flow> mFlowCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE); - private final Pool<Builder> mBuilderCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE); + private final Pool<BuilderImpl> mBuilderCache = new Pool(DEFAULT_EXTRA_CACHE_SIZE); private static final NativeAllocationRegistry sRegistry = NativeAllocationRegistry.createMalloced( @@ -509,7 +781,6 @@ public final class PerfettoTrackEventExtra { */ public void reset() { native_clear_args(mPtr); - mIsInUse = false; } private CounterInt64 getCounterInt64() { @@ -1078,4 +1349,6 @@ public final class PerfettoTrackEventExtra { private static native void native_add_arg(long ptr, long extraPtr); @CriticalNative private static native void native_clear_args(long ptr); + @FastNative + private static native void native_emit(int type, long tag, String name, long ptr); } diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java index b5251db4e539..051885e10132 100644 --- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java +++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java @@ -25,6 +25,7 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; +import android.os.UserHandle; import java.io.Closeable; import java.util.concurrent.Executor; @@ -232,6 +233,15 @@ public interface QuickAccessWalletClient extends Closeable { Drawable getTileIcon(); /** + * Returns the user that should receive the wallet intents + * + * @return UserHandle + * @hide + */ + @Nullable + UserHandle getUser(); + + /** * Returns the service label specified by {@code android:label} in the service manifest entry. * * @hide diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java index 97a4beff633f..177164296eea 100644 --- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java +++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java @@ -243,8 +243,9 @@ public class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Ser return null; } String packageName = mServiceInfo.getComponentName().getPackageName(); + int userId = mServiceInfo.getUserId(); String walletActivity = mServiceInfo.getWalletActivity(); - return createIntent(walletActivity, packageName, ACTION_VIEW_WALLET); + return createIntent(walletActivity, packageName, userId, ACTION_VIEW_WALLET); } @Override @@ -302,12 +303,15 @@ public class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Ser } String packageName = mServiceInfo.getComponentName().getPackageName(); String settingsActivity = mServiceInfo.getSettingsActivity(); - return createIntent(settingsActivity, packageName, ACTION_VIEW_WALLET_SETTINGS); + return createIntent(settingsActivity, packageName, UserHandle.myUserId(), + ACTION_VIEW_WALLET_SETTINGS); } @Nullable - private Intent createIntent(@Nullable String activityName, String packageName, String action) { - PackageManager pm = mContext.getPackageManager(); + private Intent createIntent(@Nullable String activityName, String packageName, + int userId, String action) { + Context userContext = mContext.createContextAsUser(UserHandle.of(userId), 0); + PackageManager pm = userContext.getPackageManager(); if (TextUtils.isEmpty(activityName)) { activityName = queryActivityForAction(pm, packageName, action); } @@ -361,6 +365,12 @@ public class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Ser return mServiceInfo == null ? null : mServiceInfo.getTileIcon(); } + @Nullable + @Override + public UserHandle getUser() { + return mServiceInfo == null ? null : UserHandle.of(mServiceInfo.getUserId()); + } + @Override @Nullable public CharSequence getServiceLabel() { diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java index 8a3f6ceb852b..01de54354a04 100644 --- a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java +++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java @@ -16,6 +16,10 @@ package android.service.quickaccesswallet; +import static android.permission.flags.Flags.walletRoleCrossUserEnabled; + +import static com.android.permission.flags.Flags.crossUserRoleEnabled; + import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; @@ -32,10 +36,12 @@ import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; import android.os.Binder; +import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; +import android.util.Pair; import android.util.Xml; import com.android.internal.R; @@ -59,22 +65,29 @@ class QuickAccessWalletServiceInfo { private final ServiceInfo mServiceInfo; private final ServiceMetadata mServiceMetadata; private final TileServiceMetadata mTileServiceMetadata; + private final int mUserId; private QuickAccessWalletServiceInfo( @NonNull ServiceInfo serviceInfo, @NonNull ServiceMetadata metadata, - @NonNull TileServiceMetadata tileServiceMetadata) { + @NonNull TileServiceMetadata tileServiceMetadata, + int userId) { mServiceInfo = serviceInfo; mServiceMetadata = metadata; mTileServiceMetadata = tileServiceMetadata; + mUserId = userId; } @Nullable static QuickAccessWalletServiceInfo tryCreate(@NonNull Context context) { String defaultAppPackageName = null; + int defaultAppUser = UserHandle.myUserId(); + if (isWalletRoleAvailable(context)) { - defaultAppPackageName = getDefaultWalletApp(context); + Pair<String, Integer> roleAndUser = getDefaultWalletApp(context); + defaultAppPackageName = roleAndUser.first; + defaultAppUser = roleAndUser.second; } else { ComponentName defaultPaymentApp = getDefaultPaymentApp(context); if (defaultPaymentApp == null) { @@ -83,7 +96,8 @@ class QuickAccessWalletServiceInfo { defaultAppPackageName = defaultPaymentApp.getPackageName(); } - ServiceInfo serviceInfo = getWalletServiceInfo(context, defaultAppPackageName); + ServiceInfo serviceInfo = getWalletServiceInfo(context, defaultAppPackageName, + defaultAppUser); if (serviceInfo == null) { return null; } @@ -98,15 +112,32 @@ class QuickAccessWalletServiceInfo { ServiceMetadata metadata = parseServiceMetadata(context, serviceInfo); TileServiceMetadata tileServiceMetadata = new TileServiceMetadata(parseTileServiceMetadata(context, serviceInfo)); - return new QuickAccessWalletServiceInfo(serviceInfo, metadata, tileServiceMetadata); + return new QuickAccessWalletServiceInfo(serviceInfo, metadata, tileServiceMetadata, + defaultAppUser); } - private static String getDefaultWalletApp(Context context) { + @NonNull + private static Pair<String, Integer> getDefaultWalletApp(Context context) { + UserHandle user = UserHandle.of(UserHandle.myUserId()); + final long token = Binder.clearCallingIdentity(); try { RoleManager roleManager = context.getSystemService(RoleManager.class); - List<String> roleHolders = roleManager.getRoleHolders(RoleManager.ROLE_WALLET); - return roleHolders.isEmpty() ? null : roleHolders.get(0); + + if (walletRoleCrossUserEnabled() + && crossUserRoleEnabled() + && context.checkCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL) + == PackageManager.PERMISSION_GRANTED) { + user = roleManager.getActiveUserForRole(RoleManager.ROLE_WALLET); + if (user == null) { + return new Pair<>(null, user.getIdentifier()); + } + } + List<String> roleHolders = roleManager.getRoleHoldersAsUser(RoleManager.ROLE_WALLET, + user); + return new Pair<>(roleHolders.isEmpty() ? null : roleHolders.get(0), + user.getIdentifier()); } finally { Binder.restoreCallingIdentity(token); } @@ -128,15 +159,16 @@ class QuickAccessWalletServiceInfo { return comp == null ? null : ComponentName.unflattenFromString(comp); } - private static ServiceInfo getWalletServiceInfo(Context context, String packageName) { + private static ServiceInfo getWalletServiceInfo(Context context, String packageName, + int userId) { Intent intent = new Intent(QuickAccessWalletService.SERVICE_INTERFACE); intent.setPackage(packageName); List<ResolveInfo> resolveInfos = - context.getPackageManager().queryIntentServices(intent, + context.getPackageManager().queryIntentServicesAsUser(intent, PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE | PackageManager.MATCH_DEFAULT_ONLY - | PackageManager.GET_META_DATA); + | PackageManager.GET_META_DATA, userId); return resolveInfos.isEmpty() ? null : resolveInfos.get(0).serviceInfo; } @@ -247,6 +279,9 @@ class QuickAccessWalletServiceInfo { return mServiceInfo.getComponentName(); } + int getUserId() { + return mUserId; + } /** * @return the fully qualified name of the activity that hosts the full wallet. If available, * this intent should be started with the action 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 26b0d11955d2..f5f4e4332d28 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java +++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java @@ -62,7 +62,7 @@ public class CoreDocument { // We also keep a more fine-grained BUILD number, exposed as // ID_API_LEVEL = DOCUMENT_API_LEVEL + BUILD - static final float BUILD = 0.2f; + static final float BUILD = 0.3f; @NonNull ArrayList<Operation> mOperations = new ArrayList<>(); 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 9a37a22390a2..0b6a3c415e4a 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java +++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java @@ -88,6 +88,8 @@ import com.android.internal.widget.remotecompose.core.operations.layout.TouchUpM 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.CanvasLayout; +import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleColumnLayout; +import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleRowLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.ColumnLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.RowLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.StateLayout; @@ -97,6 +99,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.modifier import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ClipRectModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentVisibilityOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.GraphicsLayerModifierOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightInModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HostActionOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HostNamedActionOperation; @@ -111,6 +114,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.modifier import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueIntegerChangeActionOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueIntegerExpressionChangeActionOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueStringChangeActionOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthInModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ZIndexModifierOperation; import com.android.internal.widget.remotecompose.core.operations.utilities.IntMap; @@ -208,7 +212,9 @@ public class Operations { 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_COLLAPSIBLE_ROW = 230; public static final int LAYOUT_COLUMN = 204; + public static final int LAYOUT_COLLAPSIBLE_COLUMN = 233; public static final int LAYOUT_CANVAS = 205; public static final int LAYOUT_CANVAS_CONTENT = 207; public static final int LAYOUT_TEXT = 208; @@ -218,6 +224,8 @@ public class Operations { public static final int MODIFIER_WIDTH = 16; public static final int MODIFIER_HEIGHT = 67; + public static final int MODIFIER_WIDTH_IN = 231; + public static final int MODIFIER_HEIGHT_IN = 232; public static final int MODIFIER_BACKGROUND = 55; public static final int MODIFIER_BORDER = 107; public static final int MODIFIER_PADDING = 58; @@ -324,6 +332,8 @@ public class Operations { map.put(MODIFIER_WIDTH, WidthModifierOperation::read); map.put(MODIFIER_HEIGHT, HeightModifierOperation::read); + map.put(MODIFIER_WIDTH_IN, WidthInModifierOperation::read); + map.put(MODIFIER_HEIGHT_IN, HeightInModifierOperation::read); map.put(MODIFIER_PADDING, PaddingModifierOperation::read); map.put(MODIFIER_BACKGROUND, BackgroundModifierOperation::read); map.put(MODIFIER_BORDER, BorderModifierOperation::read); @@ -359,7 +369,9 @@ public class Operations { map.put(LAYOUT_CONTENT, LayoutComponentContent::read); map.put(LAYOUT_BOX, BoxLayout::read); map.put(LAYOUT_COLUMN, ColumnLayout::read); + map.put(LAYOUT_COLLAPSIBLE_COLUMN, CollapsibleColumnLayout::read); map.put(LAYOUT_ROW, RowLayout::read); + map.put(LAYOUT_COLLAPSIBLE_ROW, CollapsibleRowLayout::read); map.put(LAYOUT_CANVAS, CanvasLayout::read); map.put(LAYOUT_CANVAS_CONTENT, CanvasContent::read); map.put(LAYOUT_TEXT, TextLayout::read); 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 39cc997fadc2..1cb8fefde80c 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java @@ -86,6 +86,8 @@ import com.android.internal.widget.remotecompose.core.operations.layout.LoopOper 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.CanvasLayout; +import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleColumnLayout; +import com.android.internal.widget.remotecompose.core.operations.layout.managers.CollapsibleRowLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.ColumnLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.RowLayout; import com.android.internal.widget.remotecompose.core.operations.layout.managers.StateLayout; @@ -691,6 +693,12 @@ public class RemoteComposeBuffer { return out; } + /** + * Append a path to an existing path + * + * @param id id of the path to append to + * @param path the path to append + */ public void pathAppend(int id, float... path) { PathAppend.apply(mBuffer, id, path); } @@ -772,8 +780,8 @@ public class RemoteComposeBuffer { * @param text The text to be drawn * @param start The index of the first character in text to draw * @param end (end - 1) is the index of the last character in text to draw - * @param contextStart - * @param contextEnd + * @param contextStart the context start + * @param contextEnd the context end * @param x The x-coordinate of the origin of the text being drawn * @param y The y-coordinate of the baseline of the text being drawn * @param rtl Draw RTTL @@ -798,8 +806,8 @@ public class RemoteComposeBuffer { * @param textId The text to be drawn * @param start The index of the first character in text to draw * @param end (end - 1) is the index of the last character in text to draw - * @param contextStart - * @param contextEnd + * @param contextStart the context start + * @param contextEnd the context end * @param x The x-coordinate of the origin of the text being drawn * @param y The y-coordinate of the baseline of the text being drawn * @param rtl Draw RTTL @@ -986,6 +994,11 @@ public class RemoteComposeBuffer { /////////////////////////////////////////////////////////////////////////////////////////////// + /** + * inflate the buffer into a list of operations + * + * @param operations the operations list to add to + */ public void inflateFromBuffer(@NonNull ArrayList<Operation> operations) { mBuffer.setIndex(0); while (mBuffer.available()) { @@ -1001,6 +1014,12 @@ public class RemoteComposeBuffer { } } + /** + * Read the next operation from the buffer + * + * @param buffer The buff to read + * @param operations the operations list to add to + */ public static void readNextOperation( @NonNull WireBuffer buffer, @NonNull ArrayList<Operation> operations) { int opId = buffer.readByte(); @@ -1014,6 +1033,11 @@ public class RemoteComposeBuffer { operation.read(buffer, operations); } + /** + * copy the current buffer to a new one + * + * @return A new RemoteComposeBuffer + */ @NonNull RemoteComposeBuffer copy() { ArrayList<Operation> operations = new ArrayList<>(); @@ -1022,6 +1046,11 @@ public class RemoteComposeBuffer { return copyFromOperations(operations, buffer); } + /** + * add a set theme + * + * @param theme The theme to set + */ public void setTheme(int theme) { Theme.apply(mBuffer, theme); } @@ -1040,6 +1069,14 @@ public class RemoteComposeBuffer { return buffer; } + /** + * Create a RemoteComposeBuffer from a file + * + * @param file A file + * @param remoteComposeState The RemoteComposeState + * @return A RemoteComposeBuffer + * @throws IOException if the file cannot be read + */ @NonNull public RemoteComposeBuffer fromFile( @NonNull File file, @NonNull RemoteComposeState remoteComposeState) throws IOException { @@ -1048,6 +1085,13 @@ public class RemoteComposeBuffer { return buffer; } + /** + * Create a RemoteComposeBuffer from an InputStream + * + * @param inputStream An InputStream + * @param remoteComposeState The RemoteComposeState + * @return A RemoteComposeBuffer + */ @NonNull public static RemoteComposeBuffer fromInputStream( @NonNull InputStream inputStream, @NonNull RemoteComposeState remoteComposeState) { @@ -1056,6 +1100,13 @@ public class RemoteComposeBuffer { return buffer; } + /** + * Create a RemoteComposeBuffer from an array of operations + * + * @param operations An array of operations + * @param buffer A RemoteComposeBuffer + * @return A RemoteComposeBuffer + */ @NonNull RemoteComposeBuffer copyFromOperations( @NonNull ArrayList<Operation> operations, @NonNull RemoteComposeBuffer buffer) { @@ -1834,12 +1885,12 @@ public class RemoteComposeBuffer { /** * Add a marquee modifier * - * @param iterations - * @param animationMode - * @param repeatDelayMillis - * @param initialDelayMillis - * @param spacing - * @param velocity + * @param iterations number of iterations + * @param animationMode animation mode + * @param repeatDelayMillis repeat delay + * @param initialDelayMillis initial delay + * @param spacing spacing between items + * @param velocity velocity of the marquee */ public void addModifierMarquee( int iterations, @@ -1861,14 +1912,21 @@ public class RemoteComposeBuffer { /** * Add a graphics layer * - * @param scaleX - * @param scaleY - * @param rotationX - * @param rotationY - * @param rotationZ - * @param shadowElevation - * @param transformOriginX - * @param transformOriginY + * @param scaleX scale x + * @param scaleY scale y + * @param rotationX rotation in X + * @param rotationY rotation in Y + * @param rotationZ rotation in Z + * @param shadowElevation shadow elevation + * @param transformOriginX transform origin x + * @param transformOriginY transform origin y + * @param alpha alpha value + * @param cameraDistance camera distance + * @param blendMode blend mode + * @param spotShadowColorId spot shadow color + * @param ambientShadowColorId ambient shadow color + * @param colorFilterId id of color filter + * @param renderEffectId id of render effect */ public void addModifierGraphicsLayer( float scaleX, @@ -1923,14 +1981,32 @@ public class RemoteComposeBuffer { ClipRectModifierOperation.apply(mBuffer); } + /** + * add start of loop + * + * @param indexId id of the variable + * @param from start value + * @param step step value + * @param until stop value + */ public void addLoopStart(int indexId, float from, float step, float until) { LoopOperation.apply(mBuffer, indexId, from, step, until); } + /** Add a loop end */ public void addLoopEnd() { ContainerEnd.apply(mBuffer); } + /** + * add a state layout + * + * @param componentId id of the state + * @param animationId animation id + * @param horizontal horizontal alignment + * @param vertical vertical alignment + * @param indexId index of the state + */ public void addStateLayout( int componentId, int animationId, int horizontal, int vertical, int indexId) { mLastComponentId = getComponentId(componentId); @@ -1966,6 +2042,22 @@ public class RemoteComposeBuffer { } /** + * Add a row start tag + * + * @param componentId component id + * @param animationId animation id + * @param horizontal horizontal alignment + * @param vertical vertical alignment + * @param spacedBy spacing between items + */ + public void addCollapsibleRowStart( + int componentId, int animationId, int horizontal, int vertical, float spacedBy) { + mLastComponentId = getComponentId(componentId); + CollapsibleRowLayout.apply( + mBuffer, mLastComponentId, animationId, horizontal, vertical, spacedBy); + } + + /** * Add a column start tag * * @param componentId component id @@ -1981,6 +2073,22 @@ public class RemoteComposeBuffer { } /** + * Add a column start tag + * + * @param componentId component id + * @param animationId animation id + * @param horizontal horizontal alignment + * @param vertical vertical alignment + * @param spacedBy spacing between items + */ + public void addCollapsibleColumnStart( + int componentId, int animationId, int horizontal, int vertical, float spacedBy) { + mLastComponentId = getComponentId(componentId); + CollapsibleColumnLayout.apply( + mBuffer, mLastComponentId, animationId, horizontal, vertical, spacedBy); + } + + /** * Add a canvas start tag * * @param componentId component id diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java index 43f8ea7dc78f..363b82bdf70c 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java @@ -67,8 +67,8 @@ public class RemoteComposeState implements CollectionsAccess { * Get Object based on id. The system will cache things like bitmaps Paths etc. They can be * accessed with this command * - * @param id - * @return + * @param id the id of the object + * @return the object */ @Nullable public Object getFromId(int id) { @@ -78,8 +78,8 @@ public class RemoteComposeState implements CollectionsAccess { /** * true if the cache contain this id * - * @param id - * @return + * @param id the id of the object + * @return true if the cache contain this id */ public boolean containsId(int id) { return mIntDataMap.get(id) != null; @@ -138,8 +138,8 @@ public class RemoteComposeState implements CollectionsAccess { /** * Get the path asociated with the Data * - * @param id - * @return + * @param id of path + * @return path object */ public Object getPath(int id) { return mPathMap.get(id); @@ -180,7 +180,7 @@ public class RemoteComposeState implements CollectionsAccess { /** * Adds a data Override. * - * @param id + * @param id the id of the data * @param item the new value */ public void overrideData(int id, @NonNull Object item) { @@ -222,8 +222,8 @@ public class RemoteComposeState implements CollectionsAccess { /** * Adds a float Override. * - * @param id - * @param value the new value + * @param id The id of the float + * @param value the override value */ public void overrideFloat(int id, float value) { float previous = mFloatMap.get(id); @@ -235,7 +235,12 @@ public class RemoteComposeState implements CollectionsAccess { } } - /** Insert an item in the cache */ + /** + * Insert an item in the cache + * + * @param item integer item to cache + * @return the id of the integer + */ public int cacheInteger(int item) { int id = nextId(); mIntegerMap.put(id, item); @@ -243,7 +248,12 @@ public class RemoteComposeState implements CollectionsAccess { return id; } - /** Insert an integer item in the cache */ + /** + * Insert an integer item in the cache + * + * @param id the id of the integer + * @param value the value of the integer + */ public void updateInteger(int id, int value) { if (!mIntegerOverride[id]) { int previous = mIntegerMap.get(id); @@ -292,10 +302,10 @@ public class RemoteComposeState implements CollectionsAccess { } /** - * Get the float value + * Get the color from the cache * - * @param id - * @return + * @param id The id of the color + * @return The color */ public int getColor(int id) { return mColorMap.get(id); @@ -377,6 +387,9 @@ public class RemoteComposeState implements CollectionsAccess { /** * Method to determine if a cached value has been written to the documents WireBuffer based on * its id. + * + * @param id id to check + * @return true if the value has not been written to the WireBuffer */ public boolean wasNotWritten(int id) { return !mIntWrittenMap.get(id); @@ -406,7 +419,7 @@ public class RemoteComposeState implements CollectionsAccess { * Get the next available id 0 is normal (float,int,String,color) 1 is VARIABLES 2 is * collections * - * @return + * @return return a unique id in the set */ public int nextId(int type) { if (0 == type) { @@ -418,7 +431,7 @@ public class RemoteComposeState implements CollectionsAccess { /** * Set the next id * - * @param id + * @param id set the id to increment off of */ public void setNextId(int id) { mNextId = id; @@ -440,8 +453,8 @@ public class RemoteComposeState implements CollectionsAccess { /** * Commands that listen to variables add themselves. * - * @param id - * @param variableSupport + * @param id id of variable to listen to + * @param variableSupport command that listens to variable */ public void listenToVar(int id, @NonNull VariableSupport variableSupport) { add(id, variableSupport); @@ -450,8 +463,8 @@ public class RemoteComposeState implements CollectionsAccess { /** * Is any command listening to this variable * - * @param id - * @return + * @param id The Variable id + * @return true if any command is listening to this variable */ public boolean hasListener(int id) { return mVarListeners.get(id) != null; @@ -460,8 +473,8 @@ public class RemoteComposeState implements CollectionsAccess { /** * List of Commands that need to be updated * - * @param context - * @return + * @param context The context + * @return The number of ops to update */ public int getOpsToUpdate(@NonNull RemoteContext context) { if (mVarListeners.get(RemoteContext.ID_CONTINUOUS_SEC) != null) { @@ -479,7 +492,7 @@ public class RemoteComposeState implements CollectionsAccess { /** * Set the width of the overall document on screen. * - * @param width + * @param width the width of the document in pixels */ public void setWindowWidth(float width) { updateFloat(RemoteContext.ID_WINDOW_WIDTH, width); @@ -488,12 +501,18 @@ public class RemoteComposeState implements CollectionsAccess { /** * Set the width of the overall document on screen. * - * @param height + * @param height the height of the document in pixels */ public void setWindowHeight(float height) { updateFloat(RemoteContext.ID_WINDOW_HEIGHT, height); } + /** + * Add an array access + * + * @param id The id of the array Access + * @param collection The array access + */ public void addCollection(int id, @NonNull ArrayAccess collection) { mCollectionMap.put(id & 0xFFFFF, collection); } @@ -513,10 +532,22 @@ public class RemoteComposeState implements CollectionsAccess { return mCollectionMap.get(id & 0xFFFFF).getId(index); } + /** + * adds a DataMap to the cache + * + * @param id The id of the data map + * @param map The data map + */ public void putDataMap(int id, @NonNull DataMap map) { mDataMapMap.put(id, map); } + /** + * Get the DataMap asociated with the id + * + * @param id the id of the DataMap + * @return the DataMap + */ public @Nullable DataMap getDataMap(int id) { return mDataMapMap.get(id); } @@ -526,15 +557,32 @@ public class RemoteComposeState implements CollectionsAccess { return mCollectionMap.get(id & 0xFFFFF).getLength(); } + /** + * sets the RemoteContext + * + * @param context the context + */ public void setContext(@NonNull RemoteContext context) { mRemoteContext = context; mRemoteContext.clearLastOpCount(); } + /** + * Add an object to the cache. Uses the id for the item and adds it to the cache based + * + * @param id the id of the object + * @param value the object + */ public void updateObject(int id, @NonNull Object value) { mObjectMap.put(id, value); } + /** + * Get an object from the cache + * + * @param id The id of the object + * @return The object + */ public @Nullable Object getObject(int id) { return mObjectMap.get(id); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java index 23c362830713..36e4ec1ff303 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java @@ -47,7 +47,7 @@ public abstract class RemoteContext { new RemoteComposeState(); // todo, is this a valid use of RemoteComposeState -- bbade@ @Nullable protected PaintContext mPaintContext = null; - protected float mDensity = 2.75f; + protected float mDensity = Float.NaN; @NonNull ContextMode mMode = ContextMode.UNSET; @@ -77,7 +77,7 @@ public abstract class RemoteContext { * @param density */ public void setDensity(float density) { - if (density > 0) { + if (!Float.isNaN(density) && density > 0) { mDensity = density; } } @@ -234,23 +234,60 @@ public abstract class RemoteContext { */ public abstract void addCollection(int id, @NonNull ArrayAccess collection); + /** + * put DataMap under an id + * + * @param id the id of the DataMap + * @param map the DataMap + */ public abstract void putDataMap(int id, @NonNull DataMap map); + /** + * Get a DataMap given an id + * + * @param id the id of the DataMap + * @return the DataMap + */ public abstract @Nullable DataMap getDataMap(int id); + /** + * Run an action + * + * @param id the id of the action + * @param metadata the metadata of the action + */ public abstract void runAction(int id, @NonNull String metadata); // TODO: we might add an interface to group all valid parameter types + + /** + * Run an action with a named parameter + * + * @param textId the text id of the action + * @param value the value of the parameter + */ public abstract void runNamedAction(int textId, Object value); + /** + * Put an object under an id + * + * @param mId the id of the object + * @param command the object + */ public abstract void putObject(int mId, @NonNull Object command); + /** + * Get an object given an id + * + * @param mId the id of the object + * @return the object + */ public abstract @Nullable Object getObject(int mId); /** * Add a touch listener to the context * - * @param touchExpression + * @param touchExpression the touch expression */ public void addTouchListener(TouchListener touchExpression) {} @@ -668,11 +705,24 @@ public abstract class RemoteContext { /////////////////////////////////////////////////////////////////////////////////////////////// // Click handling /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Is this a time id float + * + * @param fl the floatId to test + * @return true if it is a time id + */ public static boolean isTime(float fl) { int value = Utils.idFromNan(fl); return value >= ID_CONTINUOUS_SEC && value <= ID_DAY_OF_MONTH; } + /** + * get the time from a float id that indicates a type of time + * + * @param fl id of the type of time information requested + * @return various time information such as seconds or min + */ public static float getTime(float fl) { LocalDateTime dateTime = LocalDateTime.now(ZoneId.systemDefault()); // TODO, pass in a timezone explicitly? @@ -716,6 +766,17 @@ public abstract class RemoteContext { return fl; } + /** + * Add a click area to the doc + * + * @param id the id of the click area + * @param contentDescription the content description of the click area + * @param left the left bounds of the click area + * @param top the top bounds of the click area + * @param right the right bounds of the click area + * @param bottom the + * @param metadataId the id of the metadata string + */ public abstract void addClickArea( int id, int contentDescription, diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java index b55f25c911fe..06ef9979a267 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java @@ -102,6 +102,12 @@ public class ClipPath extends PaintOperation { return OP_CODE; } + /** + * Apply this operation to the buffer + * + * @param buffer the buffer to apply the operation to + * @param id the id of the path + */ public static void apply(@NonNull WireBuffer buffer, int id) { buffer.start(OP_CODE); buffer.writeInt(id); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java index ac6271c6328e..7a72b109b2a8 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java @@ -70,6 +70,13 @@ public class DataListFloat extends Operation implements VariableSupport, ArrayAc return "DataListFloat[" + Utils.idString(mId) + "] " + Arrays.toString(mValues); } + /** + * Write this operation to the buffer + * + * @param buffer the buffer to apply the operation to + * @param id the id of the array + * @param values the values of the array + */ public static void apply(@NonNull WireBuffer buffer, int id, @NonNull float[] values) { buffer.start(OP_CODE); buffer.writeInt(id); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java index 47cbff36d492..7e29620ec104 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java @@ -62,6 +62,13 @@ public class DataListIds extends Operation implements VariableSupport, ArrayAcce return "map[" + Utils.idString(mId) + "] \"" + Arrays.toString(mIds) + "\""; } + /** + * Write this operation to the buffer + * + * @param buffer the buffer to apply the operation to + * @param id the id of the array + * @param ids the values of the array + */ public static void apply(@NonNull WireBuffer buffer, int id, @NonNull int[] ids) { buffer.start(OP_CODE); buffer.writeInt(id); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java index ff85721027f7..33752e0b2134 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java @@ -89,6 +89,15 @@ public class DataMapIds extends Operation { return builder.toString(); } + /** + * Write this operation to the buffer + * + * @param buffer the buffer to apply the operation to + * @param id the id + * @param names the names of the variables + * @param type the types of the variables + * @param ids the ids of the variables + */ public static void apply( @NonNull WireBuffer buffer, int id, diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java index c1e2e662ca80..7f1ba6f94065 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java @@ -31,8 +31,8 @@ import java.util.List; /** Base class for commands that take 3 float */ public abstract class DrawBase2 extends PaintOperation implements VariableSupport { @NonNull protected String mName = "DrawRectBase"; - float mV1; - float mV2; + protected float mV1; + protected float mV2; float mValue1; float mValue2; @@ -76,6 +76,13 @@ public abstract class DrawBase2 extends PaintOperation implements VariableSuppor return mName + " " + floatToString(mV1) + " " + floatToString(mV2); } + /** + * Read this operation and add it to the list of operations + * + * @param maker the maker of the operation + * @param buffer the buffer to read + * @param operations the list of operations to add to + */ public static void read( @NonNull Maker maker, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) { float v1 = buffer.readFloat(); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java index 6fedea3245a2..a6bfda8beccd 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java @@ -92,6 +92,13 @@ public abstract class DrawBase3 extends PaintOperation implements VariableSuppor + floatToString(mV3); } + /** + * Read this operation and add it to the list of operations + * + * @param maker the maker of the operation + * @param buffer the buffer to read + * @param operations the list of operations to add to + */ public static void read( @NonNull Maker maker, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) { float v1 = buffer.readFloat(); 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 aa9cc68e6552..1e96bcd9cebf 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 @@ -102,6 +102,13 @@ public abstract class DrawBase4 extends PaintOperation implements VariableSuppor + floatToString(mY2Value, mY2); } + /** + * Read this operation and add it to the list of operations + * + * @param maker the maker of the operation + * @param buffer the buffer to read + * @param operations the list of operations to add to + */ public static void read( @NonNull Maker maker, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) { float v1 = buffer.readFloat(); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java index 64c2730e5f9a..bc5904584527 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java @@ -116,6 +116,13 @@ public abstract class DrawBase6 extends PaintOperation implements VariableSuppor DrawBase6 create(float v1, float v2, float v3, float v4, float v5, float v6); } + /** + * Read this operation and add it to the list of operations + * + * @param build interface to construct the component + * @param buffer the buffer to read from + * @param operations the list of operations to add to + */ public static void read( @NonNull Maker build, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) { float sv1 = buffer.readFloat(); @@ -132,13 +139,13 @@ public abstract class DrawBase6 extends PaintOperation implements VariableSuppor /** * writes out a the operation to the buffer. * - * @param v1 - * @param v2 - * @param v3 - * @param v4 - * @param v5 - * @param v6 - * @return + * @param v1 the first parameter + * @param v2 the second parameter + * @param v3 the third parameter + * @param v4 the fourth parameter + * @param v5 the fifth parameter + * @param v6 the sixth parameter + * @return the operation */ @Nullable public Operation construct(float v1, float v2, float v3, float v4, float v5, float v6) { diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java index cdb527dee460..40d3bede0912 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java @@ -137,6 +137,17 @@ public class DrawBitmap extends PaintOperation implements VariableSupport { return OP_CODE; } + /** + * Writes out the operation to the buffer + * + * @param buffer the buffer to write to + * @param id the id of the Bitmap + * @param left left most x coordinate + * @param top top most y coordinate + * @param right right most x coordinate + * @param bottom bottom most y coordinate + * @param descriptionId string id of the description + */ public static void apply( @NonNull WireBuffer buffer, int id, diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java index 638fe148d746..013dd1ae9db8 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java @@ -131,6 +131,21 @@ public class DrawBitmapInt extends PaintOperation implements AccessibleComponent return OP_CODE; } + /** + * Draw a bitmap using integer coordinates + * + * @param buffer the buffer to write to + * @param imageId the id of the bitmap + * @param srcLeft the left most pixel in the bitmap + * @param srcTop the top most pixel in the bitmap + * @param srcRight the right most pixel in the bitmap + * @param srcBottom the bottom most pixel in the bitmap + * @param dstLeft the left most pixel in the destination + * @param dstTop the top most pixel in the destination + * @param dstRight the right most pixel in the destination + * @param dstBottom the bottom most pixel in the destination + * @param cdId the content discription id + */ public static void apply( @NonNull WireBuffer buffer, int imageId, diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java index d6467c926747..e1070f97d5aa 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java @@ -217,6 +217,23 @@ public class DrawBitmapScaled extends PaintOperation return OP_CODE; } + /** + * Draw a bitmap using integer coordinates + * + * @param buffer the buffer to write to + * @param imageId the id of the image + * @param srcLeft the left most pixel in the image to draw + * @param srcTop the right most pixel in the image to draw + * @param srcRight the right most pixel in the image to draw + * @param srcBottom the bottom most pixel in the image to draw + * @param dstLeft the left most pixel in the destination + * @param dstTop the top most pixel in the destination + * @param dstRight the right most pixel in the destination + * @param dstBottom the bottom most pixel in the destination + * @param scaleType the type of scale operation + * @param scaleFactor the scalefactor to use with fixed scale + * @param cdId the content discription id + */ public static void apply( @NonNull WireBuffer buffer, int imageId, diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java index 398cf4892e12..db9c4d3efafa 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java @@ -81,6 +81,12 @@ public class DrawPath extends PaintOperation { return Operations.DRAW_PATH; } + /** + * Draw a path + * + * @param buffer the buffer to write to + * @param id the id of the path + */ public static void apply(@NonNull WireBuffer buffer, int id) { buffer.start(Operations.DRAW_PATH); buffer.writeInt(id); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java index 86f3c992f2fb..3ab4a87c614c 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java @@ -117,6 +117,15 @@ public class DrawTextOnPath extends PaintOperation implements VariableSupport { return Operations.DRAW_TEXT_ON_PATH; } + /** + * add a draw text on path operation to the buffer + * + * @param buffer the buffer to add to + * @param textId the id of the text string + * @param pathId the id of the path + * @param hOffset the horizontal offset to position the string + * @param vOffset the vertical offset to position the string + */ public static void apply( @NonNull WireBuffer buffer, int textId, int pathId, float hOffset, float vOffset) { buffer.start(OP_CODE); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java index d4d4a5ecf6b9..e2883949022c 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java @@ -127,6 +127,16 @@ public class DrawTweenPath extends PaintOperation implements VariableSupport { return Operations.DRAW_TWEEN_PATH; } + /** + * add a draw tween path operation to the buffer + * + * @param buffer the buffer to add to + * @param path1Id the first path + * @param path2Id the second path + * @param tween the amount of the tween + * @param start the start sub range to draw + * @param stop the end of the sub range to draw + */ public static void apply( @NonNull WireBuffer buffer, int path1Id, diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java index 044430d1e3c1..66daa13dd21c 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java @@ -74,6 +74,11 @@ public class MatrixRestore extends PaintOperation { return OP_CODE; } + /** + * add a matrix restore operation to the buffer + * + * @param buffer the buffer to add to + */ public static void apply(@NonNull WireBuffer buffer) { buffer.start(OP_CODE); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java index aec316aea361..ec918e8260b9 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java @@ -72,6 +72,11 @@ public class MatrixSave extends PaintOperation { return OP_CODE; } + /** + * add a matrix save operation to the buffer + * + * @param buffer the buffer to add to + */ public static void apply(@NonNull WireBuffer buffer) { buffer.start(Operations.MATRIX_SAVE); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java index daf2c5502c5d..f756b76b86c3 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java @@ -32,6 +32,7 @@ import com.android.internal.widget.remotecompose.core.operations.paint.PaintBund import java.util.List; +/** Paint data operation */ public class PaintData extends PaintOperation implements VariableSupport { private static final int OP_CODE = Operations.PAINT_VALUES; private static final String CLASS_NAME = "PaintData"; @@ -80,6 +81,12 @@ public class PaintData extends PaintOperation implements VariableSupport { return OP_CODE; } + /** + * add a paint data to the buffer + * + * @param buffer the buffer to add to + * @param paintBundle the paint bundle + */ public static void apply(@NonNull WireBuffer buffer, @NonNull PaintBundle paintBundle) { buffer.start(Operations.PAINT_VALUES); paintBundle.writeBundle(buffer); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java index 7ff879e41cac..e7cce03f0c4b 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java @@ -101,6 +101,7 @@ public class PathAppend extends PaintOperation implements VariableSupport { public static final int CUBIC = 14; public static final int CLOSE = 15; public static final int DONE = 16; + public static final int RESET = 17; public static final float MOVE_NAN = Utils.asNan(MOVE); public static final float LINE_NAN = Utils.asNan(LINE); public static final float QUADRATIC_NAN = Utils.asNan(QUADRATIC); @@ -108,6 +109,7 @@ public class PathAppend extends PaintOperation implements VariableSupport { public static final float CUBIC_NAN = Utils.asNan(CUBIC); public static final float CLOSE_NAN = Utils.asNan(CLOSE); public static final float DONE_NAN = Utils.asNan(DONE); + public static final float RESET_NAN = Utils.asNan(RESET); /** * The name of the class @@ -128,6 +130,14 @@ public class PathAppend extends PaintOperation implements VariableSupport { return OP_CODE; } + /** + * add a path append operation to the buffer. With PathCreate allows you create a path + * dynamically + * + * @param buffer add the data to this buffer + * @param id id of the path + * @param data the path data to append + */ public static void apply(@NonNull WireBuffer buffer, int id, @NonNull float[] data) { buffer.start(OP_CODE); buffer.writeInt(id); @@ -175,6 +185,10 @@ public class PathAppend extends PaintOperation implements VariableSupport { public void apply(@NonNull RemoteContext context) { float[] data = context.getPathData(mInstanceId); float[] out = mOutputPath; + if (Float.floatToRawIntBits(out[0]) == Float.floatToRawIntBits(RESET_NAN)) { + context.loadPathData(mInstanceId, new float[0]); + return; + } if (data != null) { out = new float[data.length + mOutputPath.length]; @@ -190,6 +204,12 @@ public class PathAppend extends PaintOperation implements VariableSupport { context.loadPathData(mInstanceId, out); } + /** + * Convert a path to a string + * + * @param path the path to convert + * @return text representation of path + */ @NonNull public static String pathString(@Nullable float[] path) { if (path == null) { diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java index 75562cd8fb4c..1f76639b1b1f 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java @@ -131,6 +131,14 @@ public class PathCreate extends PaintOperation implements VariableSupport { return OP_CODE; } + /** + * add a create path operation + * + * @param buffer buffer to add to + * @param id the id of the path + * @param startX the start x of the path (moveTo x,y) + * @param startY the start y of the path (moveTo x,y) + */ public static void apply(@NonNull WireBuffer buffer, int id, float startX, float startY) { buffer.start(OP_CODE); buffer.writeInt(id); @@ -165,6 +173,12 @@ public class PathCreate extends PaintOperation implements VariableSupport { .field(FLOAT, "startX", "initial start y"); } + /** + * convert a path to a string + * + * @param path path to convert (expressed as an array of floats) + * @return the text representing the path + */ @NonNull public static String pathString(@Nullable float[] path) { if (path == null) { diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java index 85a01fc7cbc7..45d99a716443 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java @@ -131,6 +131,13 @@ public class PathData extends Operation implements VariableSupport { return OP_CODE; } + /** + * add a create path operation + * + * @param buffer buffer to add to + * @param id the id of the path + * @param data the path + */ public static void apply(@NonNull WireBuffer buffer, int id, @NonNull float[] data) { buffer.start(Operations.DATA_PATH); buffer.writeInt(id); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java index d48de37996ee..5788d8f4da64 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java @@ -73,6 +73,13 @@ public class TextData extends Operation implements SerializableToString { return OP_CODE; } + /** + * add a text data operation + * + * @param buffer buffer to add to + * @param textId the id for the text + * @param text the data to encode + */ public static void apply(@NonNull WireBuffer buffer, int textId, @NonNull String text) { buffer.start(OP_CODE); buffer.writeInt(textId); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLength.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLength.java index 37ea567f5913..a6570a371f15 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLength.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLength.java @@ -50,6 +50,11 @@ public class TextLength extends Operation { return CLASS_NAME + "[" + mLengthId + "] = " + mTextId; } + /** + * The name of the class + * + * @return the name + */ public static @NonNull String name() { return CLASS_NAME; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java index d51b38924126..58cd68e2a5db 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java @@ -66,6 +66,11 @@ public class TextMeasure extends PaintOperation { return "FloatConstant[" + mId + "] = " + mTextId + " " + mType; } + /** + * The name of the class + * + * @return the name + */ public static @NonNull String name() { return CLASS_NAME; } 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 index e71cb9a51830..dcd334822010 100644 --- 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 @@ -36,10 +36,12 @@ import com.android.internal.widget.remotecompose.core.operations.layout.modifier import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentVisibilityOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.DimensionModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.GraphicsLayerModifierOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightInModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ScrollModifierOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthInModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ZIndexModifierOperation; @@ -204,6 +206,9 @@ public class LayoutComponent extends Component { mPaddingRight = 0f; mPaddingBottom = 0f; + WidthInModifierOperation widthInConstraints = null; + HeightInModifierOperation heightInConstraints = null; + for (OperationInterface op : mComponentModifiers.getList()) { if (op instanceof PaddingModifierOperation) { // We are accumulating padding modifiers to compute the margin @@ -221,6 +226,10 @@ public class LayoutComponent extends Component { mWidthModifier = (WidthModifierOperation) op; } else if (op instanceof HeightModifierOperation && mHeightModifier == null) { mHeightModifier = (HeightModifierOperation) op; + } else if (op instanceof WidthInModifierOperation) { + widthInConstraints = (WidthInModifierOperation) op; + } else if (op instanceof HeightInModifierOperation) { + heightInConstraints = (HeightInModifierOperation) op; } else if (op instanceof ZIndexModifierOperation) { mZIndexModifier = (ZIndexModifierOperation) op; } else if (op instanceof GraphicsLayerModifierOperation) { @@ -241,6 +250,12 @@ public class LayoutComponent extends Component { if (mHeightModifier == null) { mHeightModifier = new HeightModifierOperation(DimensionModifierOperation.Type.WRAP); } + if (widthInConstraints != null) { + mWidthModifier.setWidthIn(widthInConstraints); + } + if (heightInConstraints != null) { + mHeightModifier.setHeightIn(heightInConstraints); + } setWidth(computeModifierDefinedWidth(null)); setHeight(computeModifierDefinedHeight(null)); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java new file mode 100644 index 000000000000..afc41b1873ef --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleColumnLayout.java @@ -0,0 +1,175 @@ +/* + * 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 android.annotation.NonNull; +import android.annotation.Nullable; + +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.WireBuffer; +import com.android.internal.widget.remotecompose.core.operations.layout.Component; +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; + +public class CollapsibleColumnLayout extends ColumnLayout { + + public CollapsibleColumnLayout( + @Nullable 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, + horizontalPositioning, + verticalPositioning, + spacedBy); + } + + public CollapsibleColumnLayout( + @Nullable Component parent, + int componentId, + int animationId, + int horizontalPositioning, + int verticalPositioning, + float spacedBy) { + super( + parent, + componentId, + animationId, + horizontalPositioning, + verticalPositioning, + spacedBy); + } + + @NonNull + @Override + protected String getSerializedName() { + return "COLLAPSIBLE_COLUMN"; + } + + /** + * The OP_CODE for this command + * + * @return the opcode + */ + public static int id() { + return Operations.LAYOUT_COLLAPSIBLE_COLUMN; + } + + /** + * Write the operation to the buffer + * + * @param buffer wire buffer + * @param componentId component id + * @param animationId animation id (-1 if not set) + * @param horizontalPositioning horizontal positioning rules + * @param verticalPositioning vertical positioning rules + * @param spacedBy spaced by value + */ + public static void apply( + @NonNull WireBuffer buffer, + int componentId, + int animationId, + int horizontalPositioning, + int verticalPositioning, + float spacedBy) { + buffer.start(id()); + buffer.writeInt(componentId); + buffer.writeInt(animationId); + buffer.writeInt(horizontalPositioning); + buffer.writeInt(verticalPositioning); + buffer.writeFloat(spacedBy); + } + + /** + * Read this operation and add it to the list of operations + * + * @param buffer the buffer to read + * @param operations the list of operations that will be added to + */ + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { + int componentId = buffer.readInt(); + int animationId = buffer.readInt(); + int horizontalPositioning = buffer.readInt(); + int verticalPositioning = buffer.readInt(); + float spacedBy = buffer.readFloat(); + operations.add( + new CollapsibleColumnLayout( + null, + componentId, + animationId, + horizontalPositioning, + verticalPositioning, + spacedBy)); + } + + @Override + protected boolean hasVerticalIntrinsicDimension() { + return true; + } + + @Override + public void computeWrapSize( + @NonNull PaintContext context, + float maxWidth, + float maxHeight, + boolean horizontalWrap, + boolean verticalWrap, + @NonNull MeasurePass measure, + @NonNull Size size) { + super.computeWrapSize( + context, maxWidth, Float.MAX_VALUE, horizontalWrap, verticalWrap, measure, size); + } + + @Override + public boolean applyVisibility( + float selfWidth, float selfHeight, @NonNull MeasurePass measure) { + float childrenWidth = 0f; + float childrenHeight = 0f; + boolean changedVisibility = false; + for (Component child : mChildrenComponents) { + ComponentMeasure childMeasure = measure.get(child); + if (childMeasure.getVisibility() == Visibility.GONE) { + continue; + } + if (childrenHeight + childMeasure.getH() > selfHeight) { + childMeasure.setVisibility(Visibility.GONE); + changedVisibility = true; + } else { + childrenHeight += childMeasure.getH(); + childrenWidth = Math.max(childrenWidth, childMeasure.getW()); + } + } + return changedVisibility; + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java new file mode 100644 index 000000000000..0e7eb8676f46 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CollapsibleRowLayout.java @@ -0,0 +1,175 @@ +/* + * 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 android.annotation.NonNull; +import android.annotation.Nullable; + +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.WireBuffer; +import com.android.internal.widget.remotecompose.core.operations.layout.Component; +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; + +public class CollapsibleRowLayout extends RowLayout { + + public CollapsibleRowLayout( + @Nullable 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, + horizontalPositioning, + verticalPositioning, + spacedBy); + } + + public CollapsibleRowLayout( + @Nullable Component parent, + int componentId, + int animationId, + int horizontalPositioning, + int verticalPositioning, + float spacedBy) { + super( + parent, + componentId, + animationId, + horizontalPositioning, + verticalPositioning, + spacedBy); + } + + @NonNull + @Override + protected String getSerializedName() { + return "COLLAPSIBLE_ROW"; + } + + /** + * The OP_CODE for this command + * + * @return the opcode + */ + public static int id() { + return Operations.LAYOUT_COLLAPSIBLE_ROW; + } + + /** + * Write the operation to the buffer + * + * @param buffer wire buffer + * @param componentId component id + * @param animationId animation id (-1 if not set) + * @param horizontalPositioning horizontal positioning rules + * @param verticalPositioning vertical positioning rules + * @param spacedBy spaced by value + */ + public static void apply( + @NonNull WireBuffer buffer, + int componentId, + int animationId, + int horizontalPositioning, + int verticalPositioning, + float spacedBy) { + buffer.start(id()); + buffer.writeInt(componentId); + buffer.writeInt(animationId); + buffer.writeInt(horizontalPositioning); + buffer.writeInt(verticalPositioning); + buffer.writeFloat(spacedBy); + } + + /** + * Read this operation and add it to the list of operations + * + * @param buffer the buffer to read + * @param operations the list of operations that will be added to + */ + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { + int componentId = buffer.readInt(); + int animationId = buffer.readInt(); + int horizontalPositioning = buffer.readInt(); + int verticalPositioning = buffer.readInt(); + float spacedBy = buffer.readFloat(); + operations.add( + new CollapsibleRowLayout( + null, + componentId, + animationId, + horizontalPositioning, + verticalPositioning, + spacedBy)); + } + + @Override + protected boolean hasHorizontalIntrinsicDimension() { + return true; + } + + @Override + public void computeWrapSize( + @NonNull PaintContext context, + float maxWidth, + float maxHeight, + boolean horizontalWrap, + boolean verticalWrap, + @NonNull MeasurePass measure, + @NonNull Size size) { + super.computeWrapSize( + context, Float.MAX_VALUE, maxHeight, horizontalWrap, verticalWrap, measure, size); + } + + @Override + public boolean applyVisibility( + float selfWidth, float selfHeight, @NonNull MeasurePass measure) { + float childrenWidth = 0f; + float childrenHeight = 0f; + boolean changedVisibility = false; + for (Component child : mChildrenComponents) { + ComponentMeasure childMeasure = measure.get(child); + if (childMeasure.getVisibility() == Visibility.GONE) { + continue; + } + if (childrenWidth + childMeasure.getW() > selfWidth) { + childMeasure.setVisibility(Visibility.GONE); + changedVisibility = true; + } else { + childrenWidth += childMeasure.getW(); + childrenHeight = Math.max(childrenHeight, childMeasure.getH()); + } + } + return changedVisibility; + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java index f68d7b439578..4d0cbefb0c92 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java @@ -32,6 +32,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.LayoutCo import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure; import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass; import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightInModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.utils.DebugLog; import java.util.List; @@ -93,7 +94,8 @@ public class ColumnLayout extends LayoutManager { @NonNull @Override public String toString() { - return "COLUMN [" + return getSerializedName() + + " [" + mComponentId + ":" + mAnimationId @@ -213,41 +215,62 @@ public class ColumnLayout extends LayoutManager { selfHeight = mComponentModifiers.getVerticalScrollDimension() - mPaddingTop - mPaddingBottom; } - boolean hasWeights = false; - float totalWeights = 0f; - for (Component child : mChildrenComponents) { - ComponentMeasure childMeasure = measure.get(child); - if (childMeasure.getVisibility() == Visibility.GONE) { - continue; - } - 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; + boolean checkWeights = true; + while (checkWeights) { + checkWeights = false; + boolean hasWeights = false; + float totalWeights = 0f; for (Component child : mChildrenComponents) { + ComponentMeasure childMeasure = measure.get(child); + if (childMeasure.getVisibility() == Visibility.GONE) { + continue; + } if (child instanceof LayoutComponent && ((LayoutComponent) child).getHeightModifier().hasWeight()) { - ComponentMeasure childMeasure = measure.get(child); - if (childMeasure.getVisibility() == Visibility.GONE) { - continue; + 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); + if (childMeasure.getVisibility() == Visibility.GONE) { + continue; + } + float weight = ((LayoutComponent) child).getHeightModifier().getValue(); + float childHeight = (weight * availableSpace) / totalWeights; + HeightInModifierOperation heightInConstraints = + ((LayoutComponent) child).getHeightModifier().getHeightIn(); + if (heightInConstraints != null) { + float min = heightInConstraints.getMin(); + float max = heightInConstraints.getMax(); + if (min != -1) { + childHeight = Math.max(min, childHeight); + } + if (max != -1) { + childHeight = Math.min(max, childHeight); + } + } + childMeasure.setH(childHeight); + child.measure( + context, + childMeasure.getW(), + childMeasure.getW(), + childMeasure.getH(), + childMeasure.getH(), + measure); } - float weight = ((LayoutComponent) child).getHeightModifier().getValue(); - childMeasure.setH((weight * availableSpace) / totalWeights); - child.measure( - context, - childMeasure.getW(), - childMeasure.getW(), - childMeasure.getH(), - childMeasure.getH(), - measure); } } + + if (applyVisibility(selfWidth, selfHeight, measure) && hasWeights) { + checkWeights = true; + } } childrenHeight = 0f; @@ -360,6 +383,16 @@ public class ColumnLayout extends LayoutManager { return Operations.LAYOUT_COLUMN; } + /** + * Write the operation to the buffer + * + * @param buffer wire buffer + * @param componentId component id + * @param animationId animation id (-1 if not set) + * @param horizontalPositioning horizontal positioning rules + * @param verticalPositioning vertical positioning rules + * @param spacedBy spaced by value + */ public static void apply( @NonNull WireBuffer buffer, int componentId, @@ -367,7 +400,7 @@ public class ColumnLayout extends LayoutManager { int horizontalPositioning, int verticalPositioning, float spacedBy) { - buffer.start(Operations.LAYOUT_COLUMN); + buffer.start(id()); buffer.writeInt(componentId); buffer.writeInt(animationId); buffer.writeInt(horizontalPositioning); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java index edfd69cbfa96..8b52bbe5cdf8 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java @@ -43,6 +43,18 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl super(parent, componentId, animationId, x, y, width, height); } + /** + * Allows layout managers to override elements visibility + * + * @param selfWidth intrinsic width of the layout manager content + * @param selfHeight intrinsic height of the layout manager content + * @param measure measure pass + */ + public boolean applyVisibility( + float selfWidth, float selfHeight, @NonNull MeasurePass measure) { + return false; + } + /** Implemented by subclasses to provide a layout/measure pass */ public void internalLayoutMeasure(@NonNull PaintContext context, @NonNull MeasurePass measure) { // nothing here @@ -197,7 +209,7 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl } if (!hasWrap) { - if (hasHorizontalScroll()) { + if (hasHorizontalIntrinsicDimension()) { mCachedWrapSize.setWidth(0f); mCachedWrapSize.setHeight(0f); computeWrapSize( @@ -210,15 +222,19 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl mCachedWrapSize); float w = mCachedWrapSize.getWidth(); computeSize(context, 0f, w, 0, measuredHeight, measure); - mComponentModifiers.setHorizontalScrollDimension(measuredWidth, w); - } else if (hasVerticalScroll()) { + if (hasHorizontalScroll()) { + mComponentModifiers.setHorizontalScrollDimension(measuredWidth, w); + } + } else if (hasVerticalIntrinsicDimension()) { mCachedWrapSize.setWidth(0f); mCachedWrapSize.setHeight(0f); computeWrapSize( context, maxWidth, Float.MAX_VALUE, false, false, measure, mCachedWrapSize); float h = mCachedWrapSize.getHeight(); computeSize(context, 0f, measuredWidth, 0, h, measure); - mComponentModifiers.setVerticalScrollDimension(measuredHeight, h); + if (hasVerticalScroll()) { + mComponentModifiers.setVerticalScrollDimension(measuredHeight, h); + } } else { float maxChildWidth = measuredWidth - mPaddingLeft - mPaddingRight; float maxChildHeight = measuredHeight - mPaddingTop - mPaddingBottom; @@ -246,6 +262,14 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl return mComponentModifiers.hasHorizontalScroll(); } + protected boolean hasHorizontalIntrinsicDimension() { + return hasHorizontalScroll(); + } + + protected boolean hasVerticalIntrinsicDimension() { + return hasVerticalScroll(); + } + private boolean hasVerticalScroll() { return mComponentModifiers.hasVerticalScroll(); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java index b688f6e4175a..5b35c4c70702 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java @@ -32,6 +32,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.LayoutCo import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure; import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass; import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthInModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.utils.DebugLog; import java.util.List; @@ -91,7 +92,8 @@ public class RowLayout extends LayoutManager { @NonNull @Override public String toString() { - return "ROW [" + return getSerializedName() + + " [" + mComponentId + ":" + mAnimationId @@ -212,44 +214,66 @@ public class RowLayout extends LayoutManager { mComponentModifiers.getVerticalScrollDimension() - mPaddingTop - mPaddingBottom; } - boolean hasWeights = false; - float totalWeights = 0f; - for (Component child : mChildrenComponents) { - ComponentMeasure childMeasure = measure.get(child); - if (childMeasure.getVisibility() == Visibility.GONE) { - continue; - } - if (child instanceof LayoutComponent - && ((LayoutComponent) child).getWidthModifier().hasWeight()) { - hasWeights = true; - totalWeights += ((LayoutComponent) child).getWidthModifier().getValue(); - } else { - childrenWidth += childMeasure.getW(); - } - } + boolean checkWeights = true; - // TODO: need to move the weight measuring in the measure function, - // currently we'll measure unnecessarily - if (hasWeights) { - float availableSpace = selfWidth - childrenWidth; + while (checkWeights) { + checkWeights = false; + boolean hasWeights = false; + float totalWeights = 0f; for (Component child : mChildrenComponents) { + ComponentMeasure childMeasure = measure.get(child); + if (childMeasure.getVisibility() == Visibility.GONE) { + continue; + } if (child instanceof LayoutComponent && ((LayoutComponent) child).getWidthModifier().hasWeight()) { - ComponentMeasure childMeasure = measure.get(child); - if (childMeasure.getVisibility() == Visibility.GONE) { - continue; + 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); + if (childMeasure.getVisibility() == Visibility.GONE) { + continue; + } + float weight = ((LayoutComponent) child).getWidthModifier().getValue(); + float childWidth = (weight * availableSpace) / totalWeights; + WidthInModifierOperation widthInConstraints = + ((LayoutComponent) child).getWidthModifier().getWidthIn(); + if (widthInConstraints != null) { + float min = widthInConstraints.getMin(); + float max = widthInConstraints.getMax(); + if (min != -1) { + childWidth = Math.max(min, childWidth); + } + if (max != -1) { + childWidth = Math.min(max, childWidth); + } + } + childMeasure.setW(childWidth); + child.measure( + context, + childMeasure.getW(), + childMeasure.getW(), + childMeasure.getH(), + childMeasure.getH(), + measure); } - float weight = ((LayoutComponent) child).getWidthModifier().getValue(); - childMeasure.setW((weight * availableSpace) / totalWeights); - child.measure( - context, - childMeasure.getW(), - childMeasure.getW(), - childMeasure.getH(), - childMeasure.getH(), - measure); } } + + if (applyVisibility(selfWidth, selfHeight, measure) && hasWeights) { + checkWeights = true; + } } childrenWidth = 0f; @@ -363,6 +387,16 @@ public class RowLayout extends LayoutManager { return Operations.LAYOUT_ROW; } + /** + * Write the operation to the buffer + * + * @param buffer wire buffer + * @param componentId component id + * @param animationId animation id (-1 if not set) + * @param horizontalPositioning horizontal positioning rules + * @param verticalPositioning vertical positioning rules + * @param spacedBy spaced by value + */ public static void apply( @NonNull WireBuffer buffer, int componentId, @@ -370,7 +404,7 @@ public class RowLayout extends LayoutManager { int horizontalPositioning, int verticalPositioning, float spacedBy) { - buffer.start(Operations.LAYOUT_ROW); + buffer.start(id()); buffer.writeInt(componentId); buffer.writeInt(animationId); buffer.writeInt(horizontalPositioning); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java new file mode 100644 index 000000000000..c19bd2f6b7c0 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightInModifierOperation.java @@ -0,0 +1,124 @@ +/* + * 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 android.annotation.NonNull; + +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.DocumentedOperation; +import com.android.internal.widget.remotecompose.core.operations.DrawBase2; +import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer; + +import java.util.List; + +/** Set the min / max height dimension on a component */ +public class HeightInModifierOperation extends DrawBase2 implements ModifierOperation { + private static final int OP_CODE = Operations.MODIFIER_HEIGHT_IN; + public static final String CLASS_NAME = "HeightInModifierOperation"; + + /** + * Read this operation and add it to the list of operations + * + * @param buffer the buffer to read + * @param operations the list of operations that will be added to + */ + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { + Maker m = HeightInModifierOperation::new; + read(m, buffer, operations); + } + + /** + * Returns the min value + * + * @return minimum value + */ + public float getMin() { + return mV1; + } + + /** + * Returns the max value + * + * @return maximum value + */ + public float getMax() { + return mV2; + } + + /** + * The OP_CODE for this command + * + * @return the opcode + */ + public static int id() { + return OP_CODE; + } + + /** + * The name of the class + * + * @return the name + */ + @NonNull + public static String name() { + return CLASS_NAME; + } + + @Override + protected void write(@NonNull WireBuffer buffer, float v1, float v2) { + apply(buffer, v1, v2); + } + + /** + * Populate the documentation with a description of this operation + * + * @param doc to append the description to. + */ + public static void documentation(@NonNull DocumentationBuilder doc) { + doc.operation("Layout Operations", OP_CODE, "HeightInModifierOperation") + .description("Add additional constraints to the height") + .field(DocumentedOperation.FLOAT, "min", "The minimum height, -1 if not applied") + .field(DocumentedOperation.FLOAT, "max", "The maximum height, -1 if not applied"); + } + + public HeightInModifierOperation(float min, float max) { + super(min, max); + mName = CLASS_NAME; + } + + @Override + public void paint(@NonNull PaintContext context) {} + + /** + * Writes out the HeightInModifier to the buffer + * + * @param buffer buffer to write to + * @param x1 start x of DrawOval + * @param y1 start y of the DrawOval + */ + public static void apply(@NonNull WireBuffer buffer, float x1, float y1) { + write(buffer, OP_CODE, x1, y1); + } + + @Override + public void serializeToString(int indent, @NonNull StringSerializer serializer) { + serializer.append(indent, "HEIGHT_IN = [" + getMin() + ", " + getMax() + "]"); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java index ec078a9e73ea..4b50a916b9cd 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java @@ -31,6 +31,7 @@ import java.util.List; public class HeightModifierOperation extends DimensionModifierOperation { private static final int OP_CODE = Operations.MODIFIER_HEIGHT; public static final String CLASS_NAME = "HeightModifierOperation"; + private HeightInModifierOperation mHeightIn = null; /** * The name of the class @@ -110,4 +111,22 @@ public class HeightModifierOperation extends DimensionModifierOperation { .field(INT, "type", "") .field(FLOAT, "value", ""); } + + /** + * Set height in constraints + * + * @param heightInConstraints height constraints + */ + public void setHeightIn(HeightInModifierOperation heightInConstraints) { + mHeightIn = heightInConstraints; + } + + /** + * Returns height in constraints + * + * @return height in constraints + */ + public HeightInModifierOperation getHeightIn() { + return mHeightIn; + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java new file mode 100644 index 000000000000..c3624e5b3d88 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthInModifierOperation.java @@ -0,0 +1,124 @@ +/* + * 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 android.annotation.NonNull; + +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.DocumentedOperation; +import com.android.internal.widget.remotecompose.core.operations.DrawBase2; +import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer; + +import java.util.List; + +/** Set the min / max width dimension on a component */ +public class WidthInModifierOperation extends DrawBase2 implements ModifierOperation { + private static final int OP_CODE = Operations.MODIFIER_WIDTH_IN; + public static final String CLASS_NAME = "WidthInModifierOperation"; + + /** + * Read this operation and add it to the list of operations + * + * @param buffer the buffer to read + * @param operations the list of operations that will be added to + */ + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { + Maker m = WidthInModifierOperation::new; + read(m, buffer, operations); + } + + /** + * Returns the min value + * + * @return minimum value + */ + public float getMin() { + return mV1; + } + + /** + * Returns the max value + * + * @return maximum value + */ + public float getMax() { + return mV2; + } + + /** + * The OP_CODE for this command + * + * @return the opcode + */ + public static int id() { + return OP_CODE; + } + + /** + * The name of the class + * + * @return the name + */ + @NonNull + public static String name() { + return CLASS_NAME; + } + + @Override + protected void write(@NonNull WireBuffer buffer, float v1, float v2) { + apply(buffer, v1, v2); + } + + /** + * Populate the documentation with a description of this operation + * + * @param doc to append the description to. + */ + public static void documentation(@NonNull DocumentationBuilder doc) { + doc.operation("Layout Operations", OP_CODE, "WidthInModifierOperation") + .description("Add additional constraints to the width") + .field(DocumentedOperation.FLOAT, "min", "The minimum width, -1 if not applied") + .field(DocumentedOperation.FLOAT, "max", "The maximum width, -1 if not applied"); + } + + public WidthInModifierOperation(float min, float max) { + super(min, max); + mName = CLASS_NAME; + } + + @Override + public void paint(@NonNull PaintContext context) {} + + /** + * Writes out the WidthInModifier to the buffer + * + * @param buffer buffer to write to + * @param x1 start x of DrawOval + * @param y1 start y of the DrawOval + */ + public static void apply(@NonNull WireBuffer buffer, float x1, float y1) { + write(buffer, OP_CODE, x1, y1); + } + + @Override + public void serializeToString(int indent, @NonNull StringSerializer serializer) { + serializer.append(indent, "WIDTH_IN = [" + getMin() + ", " + getMax() + "]"); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java index 05305988a49f..532027ab2087 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java @@ -31,6 +31,7 @@ import java.util.List; public class WidthModifierOperation extends DimensionModifierOperation { private static final int OP_CODE = Operations.MODIFIER_WIDTH; public static final String CLASS_NAME = "WidthModifierOperation"; + private WidthInModifierOperation mWidthIn = null; /** * The name of the class @@ -110,4 +111,22 @@ public class WidthModifierOperation extends DimensionModifierOperation { .field(INT, "type", "") .field(FLOAT, "value", ""); } + + /** + * Set width in constraints + * + * @param widthInConstraints width constraints + */ + public void setWidthIn(WidthInModifierOperation widthInConstraints) { + mWidthIn = widthInConstraints; + } + + /** + * Returns width in constraints + * + * @return width in constraints + */ + public WidthInModifierOperation getWidthIn() { + return mWidthIn; + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java index b2ea0afd8fab..eb834a97c723 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java @@ -150,17 +150,47 @@ public class AnimatedFloatExpression { /** RAND_SEED operator */ public static final float RAND_SEED = asNan(OFFSET + 40); + /** NOISE_FROM operator calculate a random 0..1 number based on a seed */ + public static final float NOISE_FROM = asNan(OFFSET + 41); + + /** RANDOM_IN_RANGE random number in range */ + public static final float RAND_IN_RANGE = asNan(OFFSET + 42); + + /** SQUARE_SUM the sum of the square of two numbers */ + public static final float SQUARE_SUM = asNan(OFFSET + 43); + + /** STEP x > edge ? 1 : 0; */ + public static final float STEP = asNan(OFFSET + 44); + + /** SQUARE x*x; */ + public static final float SQUARE = asNan(OFFSET + 45); + + /** DUP x,x; */ + public static final float DUP = asNan(OFFSET + 46); + + /** HYPOT sqrt(x*x+y*y); */ + public static final float HYPOT = asNan(OFFSET + 47); + + /** SWAP y,x; */ + public static final float SWAP = asNan(OFFSET + 48); + + /** LERP (1-t)*x+t*y; */ + public static final float LERP = asNan(OFFSET + 49); + + /** SMOOTH_STEP (1-smoothstep(edge0,edge1,x)); */ + public static final float SMOOTH_STEP = asNan(OFFSET + 50); + /** LAST valid operator */ - public static final int LAST_OP = OFFSET + 40; + public static final int LAST_OP = OFFSET + 50; /** VAR1 operator */ - public static final float VAR1 = asNan(OFFSET + 41); + public static final float VAR1 = asNan(OFFSET + 51); /** VAR2 operator */ - public static final float VAR2 = asNan(OFFSET + 42); + public static final float VAR2 = asNan(OFFSET + 52); /** VAR2 operator */ - public static final float VAR3 = asNan(OFFSET + 43); + public static final float VAR3 = asNan(OFFSET + 53); // TODO SQUARE, DUP, HYPOT, SWAP // private static final float FP_PI = (float) Math.PI; @@ -399,6 +429,17 @@ public class AnimatedFloatExpression { sNames.put(k++, "RAND"); sNames.put(k++, "RAND_SEED"); + sNames.put(k++, "noise_from"); + sNames.put(k++, "rand_in_range"); + sNames.put(k++, "square_sum"); + sNames.put(k++, "step"); + sNames.put(k++, "square"); + sNames.put(k++, "dup"); + sNames.put(k++, "hypot"); + sNames.put(k++, "swap"); + sNames.put(k++, "lerp"); + sNames.put(k++, "smooth_step"); + sNames.put(k++, "a[0]"); sNames.put(k++, "a[1]"); sNames.put(k++, "a[2]"); @@ -615,9 +656,20 @@ public class AnimatedFloatExpression { private static final int OP_RAND = OFFSET + 39; private static final int OP_RAND_SEED = OFFSET + 40; - private static final int OP_FIRST_VAR = OFFSET + 41; - private static final int OP_SECOND_VAR = OFFSET + 42; - private static final int OP_THIRD_VAR = OFFSET + 43; + private static final int OP_NOISE_FROM = OFFSET + 41; + private static final int OP_RAND_IN_RANGE = OFFSET + 42; + private static final int OP_SQUARE_SUM = OFFSET + 43; + private static final int OP_STEP = OFFSET + 44; + private static final int OP_SQUARE = OFFSET + 45; + private static final int OP_DUP = OFFSET + 46; + private static final int OP_HYPOT = OFFSET + 47; + private static final int OP_SWAP = OFFSET + 48; + private static final int OP_LERP = OFFSET + 49; + private static final int OP_SMOOTH_STEP = OFFSET + 50; + + private static final int OP_FIRST_VAR = OFFSET + 51; + private static final int OP_SECOND_VAR = OFFSET + 52; + private static final int OP_THIRD_VAR = OFFSET + 53; int opEval(int sp, int id) { float[] array; @@ -824,6 +876,66 @@ public class AnimatedFloatExpression { } } return sp - 1; + case OP_NOISE_FROM: + int x = Float.floatToRawIntBits(mStack[sp]); + x = (x << 13) ^ x; // / Bitwise scrambling return + mStack[sp] = + (1.0f + - ((x * (x * x * 15731 + 789221) + 1376312589) & 0x7fffffff) + / 1073741824.0f); + return sp; + + case OP_RAND_IN_RANGE: + if (sRandom == null) { + sRandom = new Random(); + } + mStack[sp] = sRandom.nextFloat() * (mStack[sp] - mStack[sp - 1]) + mStack[sp - 1]; + return sp; + case OP_SQUARE_SUM: + mStack[sp - 1] = mStack[sp - 1] * mStack[sp - 1] + mStack[sp] * mStack[sp]; + return sp - 1; + case OP_STEP: + System.out.println(mStack[sp] + " > " + mStack[sp - 1]); + mStack[sp - 1] = (mStack[sp - 1] > mStack[sp]) ? 1f : 0f; + return sp - 1; + case OP_SQUARE: + mStack[sp] = mStack[sp] * mStack[sp]; + return sp; + case OP_DUP: + mStack[sp + 1] = mStack[sp]; + return sp + 1; + case OP_HYPOT: + mStack[sp - 1] = (float) Math.hypot(mStack[sp - 1], mStack[sp]); + return sp - 1; + case OP_SWAP: + float swap = mStack[sp - 1]; + mStack[sp - 1] = mStack[sp]; + mStack[sp] = swap; + return sp; + case OP_LERP: + float tmp1 = mStack[sp - 2]; + float tmp2 = mStack[sp - 1]; + float tmp3 = mStack[sp]; + mStack[sp - 2] = tmp1 + (tmp2 - tmp1) * tmp3; + return sp - 2; + case OP_SMOOTH_STEP: + float val3 = mStack[sp - 2]; + float max2 = mStack[sp - 1]; + float min1 = mStack[sp]; + System.out.println("val3 = " + val3 + " min1 = " + min1 + " max2 = " + max2); + if (val3 < min1) { + mStack[sp - 2] = 0f; + System.out.println("below min "); + } else if (val3 > max2) { + mStack[sp - 2] = 1f; + System.out.println("above max "); + + } else { + float v = (val3 - min1) / (max2 - min1); + System.out.println("v = " + v); + mStack[sp - 2] = v * v * (3 - 2 * v); + } + return sp - 2; case OP_FIRST_VAR: mStack[sp] = mVar[0]; diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/Easing.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/Easing.java index d8bc83eb8a2e..2b5368297dae 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/Easing.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/Easing.java @@ -19,24 +19,58 @@ package com.android.internal.widget.remotecompose.core.operations.utilities.easi public abstract class Easing { int mType; - /** get the value at point x */ + /** + * get the value at point x + * + * @param x the position at which to get the slope + * @return the value at the point + */ public abstract float get(float x); - /** get the slope of the easing function at at x */ + /** + * get the slope of the easing function at at x + * + * @param x the position at which to get the slope + * @return the slope + */ public abstract float getDiff(float x); + /** + * get the type of easing function + * + * @return the type of easing function + */ public int getType() { return mType; } + /** cubic Easing function that accelerates and decelerates */ public static final int CUBIC_STANDARD = 1; + + /** cubic Easing function that accelerates */ public static final int CUBIC_ACCELERATE = 2; + + /** cubic Easing function that decelerates */ public static final int CUBIC_DECELERATE = 3; + + /** cubic Easing function that just linearly interpolates */ public static final int CUBIC_LINEAR = 4; + + /** cubic Easing function that goes bacwards and then accelerates */ public static final int CUBIC_ANTICIPATE = 5; + + /** cubic Easing function that overshoots and then goes back */ public static final int CUBIC_OVERSHOOT = 6; + + /** cubic Easing function that you customize */ public static final int CUBIC_CUSTOM = 11; + + /** a monotonic spline Easing function that you customize */ public static final int SPLINE_CUSTOM = 12; + + /** a bouncing Easing function */ public static final int EASE_OUT_BOUNCE = 13; + + /** a elastic Easing function */ public static final int EASE_OUT_ELASTIC = 14; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java index 465c95d06726..65472c262206 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java @@ -52,16 +52,25 @@ public class FloatAnimation extends Easing { return str; } - public FloatAnimation() { - mType = CUBIC_STANDARD; - mEasingCurve = new CubicEasing(mType); - } - + /** + * Create an animation based on a float encoding of the animation + * + * @param description the float encoding of the animation + */ public FloatAnimation(@NonNull float... description) { mType = CUBIC_STANDARD; setAnimationDescription(description); } + /** + * Create an animation based on the parameters + * + * @param type The type of animation + * @param duration The duration of the animation + * @param description The float parameters describing the animation + * @param initialValue The initial value of the float (NaN if none) + * @param wrap The wrap value of the animation NaN if it does not wrap + */ public FloatAnimation( int type, float duration, @@ -139,8 +148,8 @@ public class FloatAnimation extends Easing { /** * Useful to debug the packed form of an animation string * - * @param description - * @return + * @param description the float encoding of the animation + * @return a string describing the animation */ public static String unpackAnimationToString(float[] description) { float[] mSpec = description; @@ -223,7 +232,7 @@ public class FloatAnimation extends Easing { /** * Create an animation based on a float encoding of the animation * - * @param description + * @param description the float encoding of the animation */ public void setAnimationDescription(@NonNull float[] description) { mSpec = description; @@ -288,7 +297,7 @@ public class FloatAnimation extends Easing { /** * Set the initial Value * - * @param value + * @param value the value to set */ public void setInitialValue(float value) { @@ -321,7 +330,7 @@ public class FloatAnimation extends Easing { /** * Set the target value to interpolate to * - * @param value + * @param value the value to set */ public void setTargetValue(float value) { mTargetValue = value; @@ -342,6 +351,11 @@ public class FloatAnimation extends Easing { setScaleOffset(); } + /** + * Get the target value + * + * @return the target value + */ public float getTargetValue() { return mTargetValue; } @@ -369,6 +383,11 @@ public class FloatAnimation extends Easing { return mEasingCurve.getDiff(t / mDuration) * (mTargetValue - mInitialValue); } + /** + * Get the initial value + * + * @return the initial value + */ public float getInitialValue() { return mInitialValue; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java index 06969ccd1b10..960eff2e7242 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 @@ -25,13 +25,18 @@ public class GeneralEasing extends Easing { /** * Set the curve based on the float encoding of it * - * @param data + * @param data the float encoding of the curve */ public void setCurveSpecification(@NonNull float[] data) { mEasingData = data; createEngine(); } + /** + * Get the float encoding of the curve + * + * @return the float encoding of the curve + */ public @NonNull float[] getCurveSpecification() { return mEasingData; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java index f4579a24fd44..01d64dff10f9 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java @@ -76,10 +76,10 @@ public class MonotonicCurveFit { } /** - * Get the position of all curves at time t + * Get the position of all curves at position t * - * @param t - * @param v + * @param t the point on the spline + * @param v the array to fill (for multiple curves) */ public void getPos(double t, @NonNull double[] v) { final int n = mT.length; @@ -136,10 +136,10 @@ public class MonotonicCurveFit { } /** - * Get the position of all curves at time t + * Get the position of all curves at position t * - * @param t - * @param v + * @param t the point on the spline + * @param v the array to fill */ public void getPos(double t, @NonNull float[] v) { final int n = mT.length; @@ -196,11 +196,11 @@ public class MonotonicCurveFit { } /** - * Get the position of the jth curve at time t + * Get the position of the jth curve at position t * - * @param t - * @param j - * @return + * @param t the position + * @param j the curve to get + * @return the position */ public double getPos(double t, int j) { final int n = mT.length; @@ -240,8 +240,8 @@ public class MonotonicCurveFit { /** * Get the slope of all the curves at position t * - * @param t - * @param v + * @param t the position + * @param v the array to fill */ public void getSlope(double t, @NonNull double[] v) { final int n = mT.length; @@ -271,9 +271,9 @@ public class MonotonicCurveFit { /** * Get the slope of the j curve at position t * - * @param t - * @param j - * @return + * @param t the position + * @param j the curve to get the value at + * @return the slope */ public double getSlope(double t, int j) { final int n = mT.length; @@ -297,6 +297,11 @@ public class MonotonicCurveFit { return 0; // should never reach here } + /** + * Get the time point used to create the curve + * + * @return the time points used to create the curve + */ public @NonNull double[] getTimePoints() { return mT; } @@ -332,7 +337,12 @@ public class MonotonicCurveFit { + h * t1; } - /** This builds a monotonic spline to be used as a wave function */ + /** + * This builds a monotonic spline to be used as a wave function + * + * @param configString the configuration string + * @return the curve + */ @NonNull public static MonotonicCurveFit buildWave(@NonNull String configString) { // done this way for efficiency diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicSpline.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicSpline.java index 23a664336c5f..8bb7dae2fd6a 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicSpline.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicSpline.java @@ -76,6 +76,11 @@ public class MonotonicSpline { mTangent = tangent; } + /** + * Get the value point used in the interpolator. + * + * @return the value points + */ public float[] getArray() { return mY; } @@ -83,7 +88,7 @@ public class MonotonicSpline { /** * Get the position of all curves at time t * - * @param t + * @param t the position along spline * @return position at t */ public float getPos(float t) { @@ -139,7 +144,7 @@ public class MonotonicSpline { /** * Get the slope of the curve at position t * - * @param t + * @param t the position along spline * @return slope at t */ public float getSlope(float t) { @@ -167,6 +172,11 @@ public class MonotonicSpline { return v; } + /** + * Get the time points used in the interpolator. + * + * @return the time points + */ public float[] getTimePoints() { return mT; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/SpringStopEngine.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/SpringStopEngine.java index 03e45031e515..2f1379b3e9fc 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/SpringStopEngine.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/SpringStopEngine.java @@ -42,9 +42,9 @@ public class SpringStopEngine { private float mStopThreshold; private int mBoundaryMode = 0; - public String debug(String desc, float time) { - return null; - } + // public String debug(String desc, float time) { + // return null; + // } void log(String str) { StackTraceElement s = new Throwable().getStackTrace()[1]; @@ -53,20 +53,41 @@ public class SpringStopEngine { System.out.println(line + str); } + /** */ public SpringStopEngine() {} + /** + * get the value the sping is pulling towards + * + * @return the value the sping is pulling towards + */ public float getTargetValue() { return (float) mTargetPos; } + /** + * get the value the sping is starting from + * + * @param v the value the sping is starting from + */ public void setInitialValue(float v) { mPos = v; } + /** + * set the value the sping is pulling towards + * + * @param v the value the sping is pulling towards + */ public void setTargetValue(float v) { mTargetPos = v; } + /** + * Create a sping engine with the parameters encoded as an array of floats + * + * @param parameters the parameters to use + */ public SpringStopEngine(float[] parameters) { if (parameters[0] != 0) { throw new RuntimeException(" parameter[0] should be 0"); @@ -83,9 +104,9 @@ public class SpringStopEngine { /** * Config the spring starting conditions * - * @param currentPos - * @param target - * @param currentVelocity + * @param currentPos the current position of the spring + * @param target the target position of the spring + * @param currentVelocity the current velocity of the spring */ public void springStart(float currentPos, float target, float currentVelocity) { mTargetPos = target; @@ -115,10 +136,22 @@ public class SpringStopEngine { mLastTime = 0; } + /** + * get the velocity of the spring at a time + * + * @param time the time to get the velocity at + * @return the velocity of the spring at a time + */ public float getVelocity(float time) { return (float) mV; } + /** + * get the position of the spring at a time + * + * @param time the time to get the position at + * @return the position of the spring at a time + */ public float get(float time) { compute(time - mLastTime); mLastTime = time; @@ -128,6 +161,11 @@ public class SpringStopEngine { return (float) mPos; } + /** + * get the acceleration of the spring + * + * @return the acceleration of the spring + */ public float getAcceleration() { double k = mStiffness; double c = mDamping; @@ -135,10 +173,20 @@ public class SpringStopEngine { return (float) (-k * x - c * mV) / mMass; } + /** + * get the velocity of the spring + * + * @return the velocity of the spring + */ public float getVelocity() { return 0; } + /** + * is the spring stopped + * + * @return true if the spring is stopped + */ public boolean isStopped() { double x = (mPos - mTargetPos); double k = mStiffness; @@ -149,6 +197,11 @@ public class SpringStopEngine { return max_def <= mStopThreshold; } + /** + * increment the spring position over time dt + * + * @param dt the time to increment the spring position over + */ private void compute(double dt) { if (dt <= 0) { // Nothing to compute if there's no time difference diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java index b1eb8041b0b3..376e1e9179f4 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java @@ -26,6 +26,13 @@ public class StepCurve extends Easing { // private static final boolean DEBUG = false; @NonNull private final MonotonicCurveFit mCurveFit; + /** + * Create a step curve from a series of values + * + * @param params the series of values to ease over + * @param offset the offset into the array + * @param len the length of the array to use + */ public StepCurve(@NonNull float[] params, int offset, int len) { mCurveFit = genSpline(params, offset, len); } diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java index 5de11a19799d..b17e3dc82d50 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java +++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java @@ -66,6 +66,28 @@ public class RemoteComposePlayer extends FrameLayout { } /** + * @inheritDoc + */ + public void requestLayout() { + super.requestLayout(); + + if (mInner != null) { + mInner.requestLayout(); + } + } + + /** + * @inheritDoc + */ + public void invalidate() { + super.invalidate(); + + if (mInner != null) { + mInner.invalidate(); + } + } + + /** * Returns true if the document supports drag touch events * * @return true if draggable content, false otherwise 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 970cc4a44672..334ba62636ff 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 @@ -48,7 +48,7 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta boolean mHasClickAreas = false; Point mActionDownPoint = new Point(0, 0); AndroidRemoteContext mARContext = new AndroidRemoteContext(); - float mDensity = 1f; + float mDensity = Float.NaN; long mStart = System.nanoTime(); long mLastFrameDelay = 1; @@ -68,24 +68,18 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta public RemoteComposeCanvas(Context context) { super(context); - if (USE_VIEW_AREA_CLICK) { - addOnAttachStateChangeListener(this); - } + addOnAttachStateChangeListener(this); } public RemoteComposeCanvas(Context context, AttributeSet attrs) { super(context, attrs); - if (USE_VIEW_AREA_CLICK) { - addOnAttachStateChangeListener(this); - } + addOnAttachStateChangeListener(this); } public RemoteComposeCanvas(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); setBackgroundColor(Color.WHITE); - if (USE_VIEW_AREA_CLICK) { - addOnAttachStateChangeListener(this); - } + addOnAttachStateChangeListener(this); } public void setDebug(int value) { @@ -124,6 +118,7 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta mChoreographer.postFrameCallback(mFrameCallback); } mDensity = getContext().getResources().getDisplayMetrics().density; + mARContext.setDensity(mDensity); if (mDocument == null) { return; } diff --git a/core/jni/android_os_PerfettoTrace.cpp b/core/jni/android_os_PerfettoTrace.cpp index 988aea722be3..962aefc482e4 100644 --- a/core/jni/android_os_PerfettoTrace.cpp +++ b/core/jni/android_os_PerfettoTrace.cpp @@ -23,6 +23,7 @@ #include <nativehelper/scoped_local_ref.h> #include <nativehelper/scoped_primitive_array.h> #include <nativehelper/scoped_utf_chars.h> +#include <nativehelper/utils.h> #include <tracing_sdk.h> namespace android { @@ -36,30 +37,6 @@ inline static jlong toJLong(T* ptr) { return static_cast<jlong>(reinterpret_cast<uintptr_t>(ptr)); } -static const char* fromJavaString(JNIEnv* env, jstring jstr) { - if (!jstr) return ""; - ScopedUtfChars chars(env, jstr); - - if (!chars.c_str()) { - ALOGE("Failed extracting string"); - return ""; - } - - return chars.c_str(); -} - -static void android_os_PerfettoTrace_event(JNIEnv* env, jclass, jint type, jlong cat_ptr, - jstring name, jlong extra_ptr) { - ScopedUtfChars name_utf(env, name); - if (!name_utf.c_str()) { - ALOGE("Failed extracting string"); - } - - tracing_perfetto::Category* category = toPointer<tracing_perfetto::Category>(cat_ptr); - tracing_perfetto::trace_event(type, category->get(), name_utf.c_str(), - toPointer<tracing_perfetto::Extra>(extra_ptr)); -} - static jlong android_os_PerfettoTrace_get_process_track_uuid() { return tracing_perfetto::get_process_track_uuid(); } @@ -70,20 +47,18 @@ static jlong android_os_PerfettoTrace_get_thread_track_uuid(jlong tid) { static void android_os_PerfettoTrace_activate_trigger(JNIEnv* env, jclass, jstring name, jint ttl_ms) { - ScopedUtfChars name_utf(env, name); - if (!name_utf.c_str()) { - ALOGE("Failed extracting string"); - return; - } - - tracing_perfetto::activate_trigger(name_utf.c_str(), static_cast<uint32_t>(ttl_ms)); + ScopedUtfChars name_chars = GET_UTF_OR_RETURN_VOID(env, name); + tracing_perfetto::activate_trigger(name_chars.c_str(), static_cast<uint32_t>(ttl_ms)); } static jlong android_os_PerfettoTraceCategory_init(JNIEnv* env, jclass, jstring name, jstring tag, jstring severity) { - return toJLong(new tracing_perfetto::Category(fromJavaString(env, name), - fromJavaString(env, tag), - fromJavaString(env, severity))); + ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name); + ScopedUtfChars tag_chars = GET_UTF_OR_RETURN(env, tag); + ScopedUtfChars severity_chars = GET_UTF_OR_RETURN(env, severity); + + return toJLong(new tracing_perfetto::Category(name_chars.c_str(), tag_chars.c_str(), + severity_chars.c_str())); } static jlong android_os_PerfettoTraceCategory_delete() { @@ -121,8 +96,7 @@ static const JNINativeMethod gCategoryMethods[] = { }; static const JNINativeMethod gTraceMethods[] = - {{"native_event", "(IJLjava/lang/String;J)V", (void*)android_os_PerfettoTrace_event}, - {"native_get_process_track_uuid", "()J", + {{"native_get_process_track_uuid", "()J", (void*)android_os_PerfettoTrace_get_process_track_uuid}, {"native_get_thread_track_uuid", "(J)J", (void*)android_os_PerfettoTrace_get_thread_track_uuid}, @@ -132,10 +106,11 @@ static const JNINativeMethod gTraceMethods[] = int register_android_os_PerfettoTrace(JNIEnv* env) { int res = jniRegisterNativeMethods(env, "android/os/PerfettoTrace", gTraceMethods, NELEM(gTraceMethods)); + LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register perfetto native methods."); res = jniRegisterNativeMethods(env, "android/os/PerfettoTrace$Category", gCategoryMethods, NELEM(gCategoryMethods)); - LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods."); + LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register category native methods."); return 0; } diff --git a/core/jni/android_os_PerfettoTrackEventExtra.cpp b/core/jni/android_os_PerfettoTrackEventExtra.cpp index 9adad7bca940..b8bdc8c29199 100644 --- a/core/jni/android_os_PerfettoTrackEventExtra.cpp +++ b/core/jni/android_os_PerfettoTrackEventExtra.cpp @@ -20,6 +20,7 @@ #include <log/log.h> #include <nativehelper/JNIHelp.h> #include <nativehelper/scoped_utf_chars.h> +#include <nativehelper/utils.h> #include <tracing_sdk.h> static constexpr ssize_t kMaxStrLen = 4096; @@ -34,32 +35,24 @@ inline static jlong toJLong(T* ptr) { return static_cast<jlong>(reinterpret_cast<uintptr_t>(ptr)); } -static const char* fromJavaString(JNIEnv* env, jstring jstr) { - if (!jstr) return ""; - ScopedUtfChars chars(env, jstr); - - if (!chars.c_str()) { - ALOGE("Failed extracting string"); - return ""; - } - - return chars.c_str(); -} - static jlong android_os_PerfettoTrackEventExtraArgInt64_init(JNIEnv* env, jclass, jstring name) { - return toJLong(new tracing_perfetto::DebugArg<int64_t>(fromJavaString(env, name))); + ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name); + return toJLong(new tracing_perfetto::DebugArg<int64_t>(name_chars.c_str())); } static jlong android_os_PerfettoTrackEventExtraArgBool_init(JNIEnv* env, jclass, jstring name) { - return toJLong(new tracing_perfetto::DebugArg<bool>(fromJavaString(env, name))); + ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name); + return toJLong(new tracing_perfetto::DebugArg<bool>(name_chars.c_str())); } static jlong android_os_PerfettoTrackEventExtraArgDouble_init(JNIEnv* env, jclass, jstring name) { - return toJLong(new tracing_perfetto::DebugArg<double>(fromJavaString(env, name))); + ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name); + return toJLong(new tracing_perfetto::DebugArg<double>(name_chars.c_str())); } static jlong android_os_PerfettoTrackEventExtraArgString_init(JNIEnv* env, jclass, jstring name) { - return toJLong(new tracing_perfetto::DebugArg<const char*>(fromJavaString(env, name))); + ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name); + return toJLong(new tracing_perfetto::DebugArg<const char*>(name_chars.c_str())); } static jlong android_os_PerfettoTrackEventExtraArgInt64_delete() { @@ -116,9 +109,11 @@ static void android_os_PerfettoTrackEventExtraArgDouble_set_value(jlong ptr, jdo static void android_os_PerfettoTrackEventExtraArgString_set_value(JNIEnv* env, jclass, jlong ptr, jstring val) { + ScopedUtfChars val_chars = GET_UTF_OR_RETURN_VOID(env, val); + tracing_perfetto::DebugArg<const char*>* arg = toPointer<tracing_perfetto::DebugArg<const char*>>(ptr); - arg->set_value(strdup(fromJavaString(env, val))); + arg->set_value(strdup(val_chars.c_str())); } static jlong android_os_PerfettoTrackEventExtraFieldInt64_init() { @@ -191,9 +186,11 @@ static void android_os_PerfettoTrackEventExtraFieldDouble_set_value(jlong ptr, j static void android_os_PerfettoTrackEventExtraFieldString_set_value(JNIEnv* env, jclass, jlong ptr, jlong id, jstring val) { + ScopedUtfChars val_chars = GET_UTF_OR_RETURN_VOID(env, val); + tracing_perfetto::ProtoField<const char*>* field = toPointer<tracing_perfetto::ProtoField<const char*>>(ptr); - field->set_value(id, strdup(fromJavaString(env, val))); + field->set_value(id, strdup(val_chars.c_str())); } static void android_os_PerfettoTrackEventExtraFieldNested_add_field(jlong field_ptr, @@ -234,7 +231,8 @@ static jlong android_os_PerfettoTrackEventExtraFlow_get_extra_ptr(jlong ptr) { static jlong android_os_PerfettoTrackEventExtraNamedTrack_init(JNIEnv* env, jclass, jlong id, jstring name, jlong parent_uuid) { - return toJLong(new tracing_perfetto::NamedTrack(id, parent_uuid, fromJavaString(env, name))); + ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name); + return toJLong(new tracing_perfetto::NamedTrack(id, parent_uuid, name_chars.c_str())); } static jlong android_os_PerfettoTrackEventExtraNamedTrack_delete() { @@ -248,8 +246,9 @@ static jlong android_os_PerfettoTrackEventExtraNamedTrack_get_extra_ptr(jlong pt static jlong android_os_PerfettoTrackEventExtraCounterTrack_init(JNIEnv* env, jclass, jstring name, jlong parent_uuid) { - return toJLong( - new tracing_perfetto::RegisteredTrack(1, parent_uuid, fromJavaString(env, name), true)); + ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name); + + return toJLong(new tracing_perfetto::RegisteredTrack(1, parent_uuid, name_chars.c_str(), true)); } static jlong android_os_PerfettoTrackEventExtraCounterTrack_delete() { @@ -317,6 +316,15 @@ static void android_os_PerfettoTrackEventExtra_clear_args(jlong ptr) { extra->clear_extras(); } +static void android_os_PerfettoTrackEventExtra_emit(JNIEnv* env, jclass, jint type, jlong cat_ptr, + jstring name, jlong extra_ptr) { + ScopedUtfChars name_chars = GET_UTF_OR_RETURN_VOID(env, name); + + tracing_perfetto::Category* category = toPointer<tracing_perfetto::Category>(cat_ptr); + tracing_perfetto::trace_event(type, category->get(), name_chars.c_str(), + toPointer<tracing_perfetto::Extra>(extra_ptr)); +} + static jlong android_os_PerfettoTrackEventExtraProto_init() { return toJLong(new tracing_perfetto::Proto()); } @@ -344,7 +352,9 @@ static const JNINativeMethod gExtraMethods[] = {{"native_init", "()J", (void*)android_os_PerfettoTrackEventExtra_init}, {"native_delete", "()J", (void*)android_os_PerfettoTrackEventExtra_delete}, {"native_add_arg", "(JJ)V", (void*)android_os_PerfettoTrackEventExtra_add_arg}, - {"native_clear_args", "(J)V", (void*)android_os_PerfettoTrackEventExtra_clear_args}}; + {"native_clear_args", "(J)V", (void*)android_os_PerfettoTrackEventExtra_clear_args}, + {"native_emit", "(IJLjava/lang/String;J)V", + (void*)android_os_PerfettoTrackEventExtra_emit}}; static const JNINativeMethod gProtoMethods[] = {{"native_init", "()J", (void*)android_os_PerfettoTrackEventExtraProto_init}, diff --git a/core/tests/coretests/src/android/os/PerfettoTraceTest.java b/core/tests/coretests/src/android/os/PerfettoTraceTest.java index 292f7500479b..ad28383689af 100644 --- a/core/tests/coretests/src/android/os/PerfettoTraceTest.java +++ b/core/tests/coretests/src/android/os/PerfettoTraceTest.java @@ -112,15 +112,14 @@ public class PerfettoTraceTest { long ptr = nativeStartTracing(traceConfig.toByteArray()); - PerfettoTrackEventExtra extra = PerfettoTrackEventExtra.builder() + PerfettoTrace.instant(FOO_CATEGORY, "event") .addFlow(2) .addTerminatingFlow(3) .addArg("long_val", 10000000000L) .addArg("bool_val", true) .addArg("double_val", 3.14) .addArg("string_val", FOO) - .build(); - PerfettoTrace.instant(FOO_CATEGORY, "event", extra); + .emit(); byte[] traceBytes = nativeStopTracing(ptr); @@ -163,12 +162,12 @@ public class PerfettoTraceTest { @Test @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2) - public void testDebugAnnotationsWithLamda() throws Exception { + public void testDebugAnnotationsWithLambda() throws Exception { TraceConfig traceConfig = getTraceConfig(FOO); long ptr = nativeStartTracing(traceConfig.toByteArray()); - PerfettoTrace.instant(FOO_CATEGORY, "event", e -> e.addArg("long_val", 123L)); + PerfettoTrace.instant(FOO_CATEGORY, "event").addArg("long_val", 123L).emit(); byte[] traceBytes = nativeStopTracing(ptr); @@ -203,15 +202,14 @@ public class PerfettoTraceTest { long ptr = nativeStartTracing(traceConfig.toByteArray()); - PerfettoTrackEventExtra beginExtra = PerfettoTrackEventExtra.builder() - .usingNamedTrack(FOO, PerfettoTrace.getProcessTrackUuid()) - .build(); - PerfettoTrace.begin(FOO_CATEGORY, "event", beginExtra); + PerfettoTrace.begin(FOO_CATEGORY, "event") + .usingNamedTrack(PerfettoTrace.getProcessTrackUuid(), FOO) + .emit(); - PerfettoTrackEventExtra endExtra = PerfettoTrackEventExtra.builder() - .usingNamedTrack("bar", PerfettoTrace.getThreadTrackUuid(Process.myTid())) - .build(); - PerfettoTrace.end(FOO_CATEGORY, endExtra); + + PerfettoTrace.end(FOO_CATEGORY) + .usingNamedTrack(PerfettoTrace.getThreadTrackUuid(Process.myTid()), "bar") + .emit(); Trace trace = Trace.parseFrom(nativeStopTracing(ptr)); @@ -242,26 +240,67 @@ public class PerfettoTraceTest { assertThat(hasTrackUuid).isTrue(); assertThat(mCategoryNames).contains(FOO); assertThat(mTrackNames).contains(FOO); + assertThat(mTrackNames).contains("bar"); } @Test @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2) - public void testCounter() throws Exception { + public void testProcessThreadNamedTrack() throws Exception { TraceConfig traceConfig = getTraceConfig(FOO); long ptr = nativeStartTracing(traceConfig.toByteArray()); - PerfettoTrackEventExtra intExtra = PerfettoTrackEventExtra.builder() - .usingCounterTrack(FOO, PerfettoTrace.getProcessTrackUuid()) - .setCounter(16) - .build(); - PerfettoTrace.counter(FOO_CATEGORY, intExtra); + PerfettoTrace.begin(FOO_CATEGORY, "event") + .usingProcessNamedTrack(FOO) + .emit(); - PerfettoTrackEventExtra doubleExtra = PerfettoTrackEventExtra.builder() - .usingCounterTrack("bar", PerfettoTrace.getProcessTrackUuid()) - .setCounter(3.14) - .build(); - PerfettoTrace.counter(FOO_CATEGORY, doubleExtra); + + PerfettoTrace.end(FOO_CATEGORY) + .usingThreadNamedTrack(Process.myTid(), "%s-%s", "bar", "stool") + .emit(); + + Trace trace = Trace.parseFrom(nativeStopTracing(ptr)); + + boolean hasTrackEvent = false; + boolean hasTrackUuid = false; + for (TracePacket packet: trace.getPacketList()) { + TrackEvent event; + if (packet.hasTrackEvent()) { + hasTrackEvent = true; + event = packet.getTrackEvent(); + + if (TrackEvent.Type.TYPE_SLICE_BEGIN.equals(event.getType()) + && event.hasTrackUuid()) { + hasTrackUuid = true; + } + + if (TrackEvent.Type.TYPE_SLICE_END.equals(event.getType()) + && event.hasTrackUuid()) { + hasTrackUuid &= true; + } + } + + collectInternedData(packet); + collectTrackNames(packet); + } + + assertThat(hasTrackEvent).isTrue(); + assertThat(hasTrackUuid).isTrue(); + assertThat(mCategoryNames).contains(FOO); + assertThat(mTrackNames).contains(FOO); + assertThat(mTrackNames).contains("bar-stool"); + } + + @Test + @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2) + public void testCounterSimple() throws Exception { + TraceConfig traceConfig = getTraceConfig(FOO); + + long ptr = nativeStartTracing(traceConfig.toByteArray()); + + PerfettoTrace.counter(FOO_CATEGORY, 16, FOO).emit(); + + PerfettoTrace.counter(FOO_CATEGORY, 3.14, "bar").emit(); Trace trace = Trace.parseFrom(nativeStopTracing(ptr)); @@ -297,12 +336,102 @@ public class PerfettoTraceTest { @Test @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2) + public void testCounter() throws Exception { + TraceConfig traceConfig = getTraceConfig(FOO); + + long ptr = nativeStartTracing(traceConfig.toByteArray()); + + PerfettoTrace.counter(FOO_CATEGORY, 16) + .usingCounterTrack(PerfettoTrace.getProcessTrackUuid(), FOO).emit(); + + PerfettoTrace.counter(FOO_CATEGORY, 3.14) + .usingCounterTrack(PerfettoTrace.getThreadTrackUuid(Process.myTid()), + "%s-%s", "bar", "stool").emit(); + + Trace trace = Trace.parseFrom(nativeStopTracing(ptr)); + + boolean hasTrackEvent = false; + boolean hasCounterValue = false; + boolean hasDoubleCounterValue = false; + for (TracePacket packet: trace.getPacketList()) { + TrackEvent event; + if (packet.hasTrackEvent()) { + hasTrackEvent = true; + event = packet.getTrackEvent(); + + if (TrackEvent.Type.TYPE_COUNTER.equals(event.getType()) + && event.getCounterValue() == 16) { + hasCounterValue = true; + } + + if (TrackEvent.Type.TYPE_COUNTER.equals(event.getType()) + && event.getDoubleCounterValue() == 3.14) { + hasDoubleCounterValue = true; + } + } + + collectTrackNames(packet); + } + + assertThat(hasTrackEvent).isTrue(); + assertThat(hasCounterValue).isTrue(); + assertThat(hasDoubleCounterValue).isTrue(); + assertThat(mTrackNames).contains(FOO); + assertThat(mTrackNames).contains("bar-stool"); + } + + @Test + @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2) + public void testProcessThreadCounter() throws Exception { + TraceConfig traceConfig = getTraceConfig(FOO); + + long ptr = nativeStartTracing(traceConfig.toByteArray()); + + PerfettoTrace.counter(FOO_CATEGORY, 16).usingProcessCounterTrack(FOO).emit(); + + PerfettoTrace.counter(FOO_CATEGORY, 3.14) + .usingThreadCounterTrack(Process.myTid(), "%s-%s", "bar", "stool").emit(); + + Trace trace = Trace.parseFrom(nativeStopTracing(ptr)); + + boolean hasTrackEvent = false; + boolean hasCounterValue = false; + boolean hasDoubleCounterValue = false; + for (TracePacket packet: trace.getPacketList()) { + TrackEvent event; + if (packet.hasTrackEvent()) { + hasTrackEvent = true; + event = packet.getTrackEvent(); + + if (TrackEvent.Type.TYPE_COUNTER.equals(event.getType()) + && event.getCounterValue() == 16) { + hasCounterValue = true; + } + + if (TrackEvent.Type.TYPE_COUNTER.equals(event.getType()) + && event.getDoubleCounterValue() == 3.14) { + hasDoubleCounterValue = true; + } + } + + collectTrackNames(packet); + } + + assertThat(hasTrackEvent).isTrue(); + assertThat(hasCounterValue).isTrue(); + assertThat(hasDoubleCounterValue).isTrue(); + assertThat(mTrackNames).contains(FOO); + assertThat(mTrackNames).contains("bar-stool"); + } + + @Test + @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2) public void testProto() throws Exception { TraceConfig traceConfig = getTraceConfig(FOO); long ptr = nativeStartTracing(traceConfig.toByteArray()); - PerfettoTrackEventExtra extra5 = PerfettoTrackEventExtra.builder() + PerfettoTrace.instant(FOO_CATEGORY, "event_proto") .beginProto() .beginNested(33L) .addField(4L, 2L) @@ -310,8 +439,7 @@ public class PerfettoTraceTest { .endNested() .addField(2001, "AIDL::IActivityManager") .endProto() - .build(); - PerfettoTrace.instant(FOO_CATEGORY, "event_proto", extra5); + .emit(); byte[] traceBytes = nativeStopTracing(ptr); @@ -351,7 +479,7 @@ public class PerfettoTraceTest { long ptr = nativeStartTracing(traceConfig.toByteArray()); - PerfettoTrackEventExtra extra6 = PerfettoTrackEventExtra.builder() + PerfettoTrace.instant(FOO_CATEGORY, "event_proto_nested") .beginProto() .beginNested(29L) .beginNested(4L) @@ -364,8 +492,7 @@ public class PerfettoTraceTest { .endNested() .endNested() .endProto() - .build(); - PerfettoTrace.instant(FOO_CATEGORY, "event_proto_nested", extra6); + .emit(); byte[] traceBytes = nativeStopTracing(ptr); @@ -413,8 +540,7 @@ public class PerfettoTraceTest { long ptr = nativeStartTracing(traceConfig.toByteArray()); - PerfettoTrackEventExtra extra = PerfettoTrackEventExtra.builder().build(); - PerfettoTrace.instant(FOO_CATEGORY, "event_trigger", extra); + PerfettoTrace.instant(FOO_CATEGORY, "event_trigger").emit(); PerfettoTrace.activateTrigger(FOO, 1000); @@ -439,49 +565,21 @@ public class PerfettoTraceTest { @Test @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2) - public void testMultipleExtras() throws Exception { - boolean hasException = false; - try { - PerfettoTrackEventExtra.builder(); - - // Unclosed extra will throw an exception here - PerfettoTrackEventExtra.builder(); - } catch (Exception e) { - hasException = true; - } - - try { - PerfettoTrackEventExtra.builder().build(); - - // Closed extra but unused (reset hasn't been called internally) will throw an exception - // here. - PerfettoTrackEventExtra.builder(); - } catch (Exception e) { - hasException &= true; - } - - assertThat(hasException).isTrue(); - } - - @Test - @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2) public void testRegister() throws Exception { TraceConfig traceConfig = getTraceConfig(BAR); Category barCategory = new Category(BAR); long ptr = nativeStartTracing(traceConfig.toByteArray()); - PerfettoTrackEventExtra beforeExtra = PerfettoTrackEventExtra.builder() + PerfettoTrace.instant(barCategory, "event") .addArg("before", 1) - .build(); - PerfettoTrace.instant(barCategory, "event", beforeExtra); + .emit(); barCategory.register(); - PerfettoTrackEventExtra afterExtra = PerfettoTrackEventExtra.builder() + PerfettoTrace.instant(barCategory, "event") .addArg("after", 1) - .build(); - PerfettoTrace.instant(barCategory, "event", afterExtra); + .emit(); byte[] traceBytes = nativeStopTracing(ptr); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index e8add56619c4..408160d30467 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -1154,9 +1154,12 @@ public abstract class WMShellModule { Context context, ShellInit shellInit, Transitions transitions, - DesktopModeEventLogger desktopModeEventLogger) { + DesktopModeEventLogger desktopModeEventLogger, + Optional<DesktopTasksLimiter> desktopTasksLimiter, + ShellTaskOrganizer shellTaskOrganizer) { return new DesktopModeLoggerTransitionObserver( - context, shellInit, transitions, desktopModeEventLogger); + context, shellInit, transitions, desktopModeEventLogger, + desktopTasksLimiter, shellTaskOrganizer); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt index e8f9a789bb98..68bdbd1758b3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt @@ -467,9 +467,13 @@ class DesktopModeEventLogger { FrameworkStatsLog .DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_TASK_LIMIT ), - MINIMIZE_BUTTON( // TODO(b/356843241): use this enum value + MINIMIZE_BUTTON( FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_BUTTON ), + KEY_GESTURE( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_KEY_GESTURE + ), } // Default value used when the task was not unminimized. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt index 1ddb834399cb..9334898fdb93 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt @@ -30,6 +30,7 @@ import com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.annotations.ShellMainThread @@ -125,7 +126,9 @@ class DesktopModeKeyGestureHandler( KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW -> { logV("Key gesture MINIMIZE_FREEFORM_WINDOW is handled") getGloballyFocusedFreeformTask()?.let { - mainExecutor.execute { desktopTasksController.get().minimizeTask(it) } + mainExecutor.execute { + desktopTasksController.get().minimizeTask(it, MinimizeReason.KEY_GESTURE) + } } return true } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt index c09504ee3725..2dd89c790b58 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt @@ -36,6 +36,7 @@ import androidx.core.util.isNotEmpty import androidx.core.util.plus import androidx.core.util.putAll import com.android.internal.protolog.ProtoLog +import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason @@ -52,6 +53,8 @@ import com.android.wm.shell.shared.TransitionUtil import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions +import java.util.Optional +import kotlin.jvm.optionals.getOrNull /** * A [Transitions.TransitionObserver] that observes transitions and the proposed changes to log @@ -63,6 +66,8 @@ class DesktopModeLoggerTransitionObserver( shellInit: ShellInit, private val transitions: Transitions, private val desktopModeEventLogger: DesktopModeEventLogger, + private val desktopTasksLimiter: Optional<DesktopTasksLimiter>, + private val shellTaskOrganizer: ShellTaskOrganizer, ) : Transitions.TransitionObserver { init { @@ -141,6 +146,7 @@ class DesktopModeLoggerTransitionObserver( // identify if we need to log any changes and update the state of visible freeform tasks identifyLogEventAndUpdateState( + transition = transition, transitionInfo = info, preTransitionVisibleFreeformTasks = visibleFreeformTaskInfos, postTransitionVisibleFreeformTasks = postTransitionVisibleFreeformTasks, @@ -227,6 +233,7 @@ class DesktopModeLoggerTransitionObserver( * state and update it */ private fun identifyLogEventAndUpdateState( + transition: IBinder, transitionInfo: TransitionInfo, preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>, postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>, @@ -238,6 +245,7 @@ class DesktopModeLoggerTransitionObserver( ) { // Sessions is finishing, log task updates followed by an exit event identifyAndLogTaskUpdates( + transition, transitionInfo, preTransitionVisibleFreeformTasks, postTransitionVisibleFreeformTasks, @@ -255,6 +263,7 @@ class DesktopModeLoggerTransitionObserver( desktopModeEventLogger.logSessionEnter(getEnterReason(transitionInfo)) identifyAndLogTaskUpdates( + transition, transitionInfo, preTransitionVisibleFreeformTasks, postTransitionVisibleFreeformTasks, @@ -262,6 +271,7 @@ class DesktopModeLoggerTransitionObserver( } else if (isSessionActive) { // Session is neither starting, nor finishing, log task updates if there are any identifyAndLogTaskUpdates( + transition, transitionInfo, preTransitionVisibleFreeformTasks, postTransitionVisibleFreeformTasks, @@ -275,6 +285,7 @@ class DesktopModeLoggerTransitionObserver( /** Compare the old and new state of taskInfos and identify and log the changes */ private fun identifyAndLogTaskUpdates( + transition: IBinder, transitionInfo: TransitionInfo, preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>, postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>, @@ -310,12 +321,9 @@ class DesktopModeLoggerTransitionObserver( // find old tasks that were removed preTransitionVisibleFreeformTasks.forEach { taskId, taskInfo -> if (!postTransitionVisibleFreeformTasks.containsKey(taskId)) { - val minimizeReason = - if (transitionInfo.type == Transitions.TRANSIT_MINIMIZE) { - MinimizeReason.MINIMIZE_BUTTON - } else { - null - } + // The task is no longer visible, it might have been minimized, get the minimize + // reason (if any) + val minimizeReason = getMinimizeReason(transition, transitionInfo, taskInfo) val taskUpdate = buildTaskUpdateForTask( taskInfo, @@ -336,6 +344,21 @@ class DesktopModeLoggerTransitionObserver( } } + private fun getMinimizeReason( + transition: IBinder, + transitionInfo: TransitionInfo, + taskInfo: TaskInfo, + ): MinimizeReason? { + if (transitionInfo.type == Transitions.TRANSIT_MINIMIZE) { + return MinimizeReason.MINIMIZE_BUTTON + } + val minimizingTask = desktopTasksLimiter.getOrNull()?.getMinimizingTask(transition) + if (minimizingTask?.taskId == taskInfo.taskId) { + return minimizingTask.minimizeReason + } + return null + } + private fun buildTaskUpdateForTask( taskInfo: TaskInfo, visibleTasks: Int, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt index c975533abf24..fa696682de28 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt @@ -24,8 +24,8 @@ import android.util.SparseArray import android.view.Display.INVALID_DISPLAY import android.window.DesktopModeFlags import androidx.core.util.forEach -import androidx.core.util.keyIterator import androidx.core.util.valueIterator +import com.android.internal.annotations.VisibleForTesting import com.android.internal.protolog.ProtoLog import com.android.window.flags.Flags import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository @@ -43,26 +43,36 @@ class DesktopRepository( @ShellMainThread private val mainCoroutineScope: CoroutineScope, val userId: Int, ) { + /** A display that supports desktops. */ + private data class DesktopDisplay( + val displayId: Int, + val orderedDesks: MutableSet<Desk> = mutableSetOf(), + // TODO: b/389960283 - update on desk activation / deactivation. + var activeDeskId: Int? = null, + ) + /** - * Task data tracked per desktop. + * Task data tracked per desk. * - * @property activeTasks task ids of active tasks currently or previously visible in Desktop - * mode session. Tasks become inactive when task closes or when desktop mode session ends. + * @property activeTasks task ids of active tasks currently or previously visible in the desk. + * Tasks become inactive when task closes or when the desk becomes inactive. * @property visibleTasks task ids for active freeform tasks that are currently visible. There - * might be other active tasks in desktop mode that are not visible. + * might be other active tasks in a desk that are not visible. * @property minimizedTasks task ids for active freeform tasks that are currently minimized. * @property closingTasks task ids for tasks that are going to close, but are currently visible. * @property freeformTasksInZOrder list of current freeform task ids ordered from top to bottom - * @property fullImmersiveTaskId the task id of the desktop task that is in full-immersive mode. + * @property fullImmersiveTaskId the task id of the desk's task that is in full-immersive mode. * @property topTransparentFullscreenTaskId the task id of any current top transparent - * fullscreen task launched on top of Desktop Mode. Cleared when the transparent task is - * closed or sent to back. (top is at index 0). + * fullscreen task launched on top of the desk. Cleared when the transparent task is closed or + * sent to back. (top is at index 0). * @property pipTaskId the task id of PiP task entered while in Desktop Mode. - * @property pipShouldKeepDesktopActive whether an active PiP window should keep the Desktop - * Mode session active. Only false when we are explicitly exiting Desktop Mode (via user - * action) while there is an active PiP window. + * @property pipShouldKeepDesktopActive whether an active PiP window should keep the desk + * active. Only false when we are explicitly exiting Desktop Mode (via user action) while + * there is an active PiP window. */ - private data class DesktopTaskData( + private data class Desk( + val deskId: Int, + val displayId: Int, val activeTasks: ArraySet<Int> = ArraySet(), val visibleTasks: ArraySet<Int> = ArraySet(), val minimizedTasks: ArraySet<Int> = ArraySet(), @@ -72,10 +82,13 @@ class DesktopRepository( var fullImmersiveTaskId: Int? = null, var topTransparentFullscreenTaskId: Int? = null, var pipTaskId: Int? = null, + // TODO: b/389960283 - consolidate this with [DesktopDisplay#activeDeskId]. var pipShouldKeepDesktopActive: Boolean = true, ) { - fun deepCopy(): DesktopTaskData = - DesktopTaskData( + fun deepCopy(): Desk = + Desk( + deskId = deskId, + displayId = displayId, activeTasks = ArraySet(activeTasks), visibleTasks = ArraySet(visibleTasks), minimizedTasks = ArraySet(minimizedTasks), @@ -87,6 +100,8 @@ class DesktopRepository( pipShouldKeepDesktopActive = pipShouldKeepDesktopActive, ) + // TODO: b/362720497 - remove when multi-desktops is enabled where instances aren't + // reusable. fun clear() { activeTasks.clear() visibleTasks.clear() @@ -121,11 +136,11 @@ class DesktopRepository( private var desktopGestureExclusionListener: Consumer<Region>? = null private var desktopGestureExclusionExecutor: Executor? = null - private val desktopTaskDataByDisplayId = - object : SparseArray<DesktopTaskData>() { - /** Gets [DesktopTaskData] for existing [displayId] or creates a new one. */ - fun getOrCreate(displayId: Int): DesktopTaskData = - this[displayId] ?: DesktopTaskData().also { this[displayId] = it } + private val desktopData: DesktopData = + if (Flags.enableMultipleDesktopsBackend()) { + MultiDesktopData() + } else { + SingleDesktopData() } /** Adds [activeTasksListener] to be notified of updates to active tasks. */ @@ -136,10 +151,16 @@ class DesktopRepository( /** Adds [visibleTasksListener] to be notified of updates to visible tasks. */ fun addVisibleTasksListener(visibleTasksListener: VisibleTasksListener, executor: Executor) { visibleTasksListeners[visibleTasksListener] = executor - desktopTaskDataByDisplayId.keyIterator().forEach { - val visibleTaskCount = getVisibleTaskCount(it) - executor.execute { visibleTasksListener.onTasksVisibilityChanged(it, visibleTaskCount) } - } + desktopData + .desksSequence() + .groupBy { it.displayId } + .keys + .forEach { displayId -> + val visibleTaskCount = getVisibleTaskCount(displayId) + executor.execute { + visibleTasksListener.onTasksVisibilityChanged(displayId, visibleTaskCount) + } + } } /** Updates tasks changes on all the active task listeners for given display id. */ @@ -147,9 +168,8 @@ class DesktopRepository( activeTasksListeners.onEach { it.onActiveTasksChanged(displayId) } } - /** Returns a list of all [DesktopTaskData] in the repository. */ - private fun desktopTaskDataSequence(): Sequence<DesktopTaskData> = - desktopTaskDataByDisplayId.valueIterator().asSequence() + /** Returns a list of all [Desk]s in the repository. */ + private fun desksSequence(): Sequence<Desk> = desktopData.desksSequence() /** Adds [regionListener] to inform about changes to exclusion regions for all Desktop tasks. */ fun setExclusionRegionListener(regionListener: Consumer<Region>, executor: Executor) { @@ -179,99 +199,183 @@ class DesktopRepository( visibleTasksListeners.remove(visibleTasksListener) } - /** Adds task with [taskId] to the list of freeform tasks on [displayId]. */ + /** Adds the given desk under the given display. */ + fun addDesk(displayId: Int, deskId: Int) { + desktopData.getOrCreateDesk(displayId, deskId) + } + + /** Returns the default desk in the given display. */ + fun getDefaultDesk(displayId: Int): Int? = desktopData.getDefaultDesk(displayId)?.deskId + + /** Sets the given desk as the active one in the given display. */ + fun setActiveDesk(displayId: Int, deskId: Int) { + desktopData.setActiveDesk(displayId = displayId, deskId = deskId) + } + + /** + * Adds task with [taskId] to the list of freeform tasks on [displayId]'s active desk. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun addTask(displayId: Int, taskId: Int, isVisible: Boolean) { addOrMoveFreeformTaskToTop(displayId, taskId) addActiveTask(displayId, taskId) updateTask(displayId, taskId, isVisible) } - /** Adds task with [taskId] to the list of active tasks on [displayId]. */ + /** + * Adds task with [taskId] to the list of active tasks on [displayId]'s active desk. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ private fun addActiveTask(displayId: Int, taskId: Int) { - // Removes task if it is active on another display excluding [displayId]. - removeActiveTask(taskId, excludedDisplayId = displayId) + val activeDeskId = + desktopData.getActiveDesk(displayId)?.deskId + ?: error("Expected active desk in display: $displayId") + + // Removes task if it is active on another desk excluding [activeDesk]. + removeActiveTask(taskId, excludedDeskId = activeDeskId) - if (desktopTaskDataByDisplayId.getOrCreate(displayId).activeTasks.add(taskId)) { - logD("Adds active task=%d displayId=%d", taskId, displayId) + if (desktopData.getOrCreateDesk(displayId, activeDeskId).activeTasks.add(taskId)) { + logD("Adds active task=%d displayId=%d deskId=%d", taskId, displayId, activeDeskId) updateActiveTasksListeners(displayId) } } - /** Removes task from active task list of displays excluding the [excludedDisplayId]. */ - fun removeActiveTask(taskId: Int, excludedDisplayId: Int? = null) { - desktopTaskDataByDisplayId.forEach { displayId, desktopTaskData -> - if ((displayId != excludedDisplayId) && desktopTaskData.activeTasks.remove(taskId)) { - logD("Removed active task=%d displayId=%d", taskId, displayId) - updateActiveTasksListeners(displayId) + /** Removes task from active task list of desks excluding the [excludedDeskId]. */ + @VisibleForTesting + fun removeActiveTask(taskId: Int, excludedDeskId: Int? = null) { + val affectedDisplays = mutableSetOf<Int>() + desktopData.forAllDesks { displayId, desk -> + if (desk.deskId != excludedDeskId && desk.activeTasks.remove(taskId)) { + logD( + "Removed active task=%d displayId=%d deskId=%d", + taskId, + displayId, + desk.deskId, + ) + affectedDisplays.add(displayId) } } + affectedDisplays.forEach { displayId -> updateActiveTasksListeners(displayId) } } - /** Adds given task to the closing task list for [displayId]. */ + /** + * Adds given task to the closing task list for [displayId]'s active desk. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun addClosingTask(displayId: Int, taskId: Int) { - if (desktopTaskDataByDisplayId.getOrCreate(displayId).closingTasks.add(taskId)) { - logD("Added closing task=%d displayId=%d", taskId, displayId) + val activeDeskId = + desktopData.getActiveDesk(displayId)?.deskId + ?: error("Expected active desk in display: $displayId") + if (desktopData.getOrCreateDesk(displayId, activeDeskId).closingTasks.add(taskId)) { + logD("Added closing task=%d displayId=%d deskId=%d", taskId, displayId, activeDeskId) } else { // If the task hasn't been removed from closing list after it disappeared. - logW("Task with taskId=%d displayId=%d is already closing", taskId, displayId) + logW( + "Task with taskId=%d displayId=%d deskId=%d is already closing", + taskId, + displayId, + activeDeskId, + ) } } - /** Removes task from the list of closing tasks for [displayId]. */ + /** Removes task from the list of closing tasks for all desks. */ fun removeClosingTask(taskId: Int) { - desktopTaskDataByDisplayId.forEach { displayId, taskInfo -> - if (taskInfo.closingTasks.remove(taskId)) { - logD("Removed closing task=%d displayId=%d", taskId, displayId) + desktopData.forAllDesks { desk -> + if (desk.closingTasks.remove(taskId)) { + logD("Removed closing task=%d deskId=%d", taskId, desk.deskId) } } } - fun isActiveTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.activeTasks } + fun isActiveTask(taskId: Int) = desksSequence().any { taskId in it.activeTasks } - fun isClosingTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.closingTasks } + fun isClosingTask(taskId: Int) = desksSequence().any { taskId in it.closingTasks } - fun isVisibleTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.visibleTasks } + fun isVisibleTask(taskId: Int) = desksSequence().any { taskId in it.visibleTasks } - fun isMinimizedTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.minimizedTasks } + fun isMinimizedTask(taskId: Int) = desksSequence().any { taskId in it.minimizedTasks } - /** Checks if a task is the only visible, non-closing, non-minimized task on its display. */ + /** + * Checks if a task is the only visible, non-closing, non-minimized task on the active desk of + * the given display, or any display's active desk if [displayId] is [INVALID_DISPLAY]. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun isOnlyVisibleNonClosingTask(taskId: Int, displayId: Int = INVALID_DISPLAY): Boolean { - val seq = + val activeDesks = if (displayId != INVALID_DISPLAY) { - sequenceOf(desktopTaskDataByDisplayId[displayId]).filterNotNull() + setOfNotNull(desktopData.getActiveDesk(displayId)) } else { - desktopTaskDataSequence() + desktopData.getAllActiveDesks() } - return seq.any { - it.visibleTasks.subtract(it.closingTasks).subtract(it.minimizedTasks).singleOrNull() == - taskId + return activeDesks.any { desk -> + desk.visibleTasks + .subtract(desk.closingTasks) + .subtract(desk.minimizedTasks) + .singleOrNull() == taskId } } + /** + * Returns the active tasks in the given display's active desk. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ + @VisibleForTesting fun getActiveTasks(displayId: Int): ArraySet<Int> = - ArraySet(desktopTaskDataByDisplayId[displayId]?.activeTasks) + ArraySet(desktopData.getActiveDesk(displayId)?.activeTasks) + /** + * Returns the minimized tasks in the given display's active desk. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun getMinimizedTasks(displayId: Int): ArraySet<Int> = - ArraySet(desktopTaskDataByDisplayId[displayId]?.minimizedTasks) + ArraySet(desktopData.getActiveDesk(displayId)?.minimizedTasks) - /** Returns all active non-minimized tasks for [displayId] ordered from top to bottom. */ + /** + * Returns all active non-minimized tasks for [displayId] ordered from top to bottom. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun getExpandedTasksOrdered(displayId: Int): List<Int> = getFreeformTasksInZOrder(displayId).filter { !isMinimizedTask(it) } - /** Returns the count of active non-minimized tasks for [displayId]. */ + /** + * Returns the count of active non-minimized tasks for [displayId]. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun getExpandedTaskCount(displayId: Int): Int { return getActiveTasks(displayId).count { !isMinimizedTask(it) } } - /** Returns a list of freeform tasks, ordered from top-bottom (top at index 0). */ + /** + * Returns a list of freeform tasks, ordered from top-bottom (top at index 0). + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ + @VisibleForTesting fun getFreeformTasksInZOrder(displayId: Int): ArrayList<Int> = - ArrayList(desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder ?: emptyList()) + ArrayList(desktopData.getActiveDesk(displayId)?.freeformTasksInZOrder ?: emptyList()) + + /** Returns the tasks inside the given desk. */ + fun getActiveTaskIdsInDesk(deskId: Int): Set<Int> = + desktopData.getDesk(deskId)?.activeTasks?.toSet() + ?: run { + logW("getTasksInDesk: could not find desk: deskId=%d", deskId) + emptySet() + } /** Removes task from visible tasks of all displays except [excludedDisplayId]. */ private fun removeVisibleTask(taskId: Int, excludedDisplayId: Int? = null) { - desktopTaskDataByDisplayId.forEach { displayId, data -> - if ((displayId != excludedDisplayId) && data.visibleTasks.remove(taskId)) { - notifyVisibleTaskListeners(displayId, data.visibleTasks.size) + desktopData.forAllDesks { displayId, desk -> + if (displayId != excludedDisplayId && desk.visibleTasks.remove(taskId)) { + notifyVisibleTaskListeners(displayId, desk.visibleTasks.size) } } } @@ -281,6 +385,8 @@ class DesktopRepository( * * If task was visible on a different display with a different [displayId], removes from the set * of visible tasks on that display and notifies listeners. + * + * TODO: b/389960283 - add explicit [deskId] argument. */ fun updateTask(displayId: Int, taskId: Int, isVisible: Boolean) { logD("updateTask taskId=%d, displayId=%d, isVisible=%b", taskId, displayId, isVisible) @@ -295,10 +401,11 @@ class DesktopRepository( } val prevCount = getVisibleTaskCount(displayId) if (isVisible) { - desktopTaskDataByDisplayId.getOrCreate(displayId).visibleTasks.add(taskId) + desktopData.getActiveDesk(displayId)?.visibleTasks?.add(taskId) + ?: error("Expected non-null active desk in display $displayId") unminimizeTask(displayId, taskId) } else { - desktopTaskDataByDisplayId[displayId]?.visibleTasks?.remove(taskId) + desktopData.getActiveDesk(displayId)?.visibleTasks?.remove(taskId) } val newCount = getVisibleTaskCount(displayId) if (prevCount != newCount) { @@ -316,57 +423,94 @@ class DesktopRepository( } } - /** Set whether the given task is the Desktop-entered PiP task in this display. */ + /** + * Set whether the given task is the Desktop-entered PiP task in this display's active desk. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun setTaskInPip(displayId: Int, taskId: Int, enterPip: Boolean) { - val desktopData = desktopTaskDataByDisplayId.getOrCreate(displayId) + val activeDesk = + desktopData.getActiveDesk(displayId) + ?: error("Expected active desk in display: $displayId") if (enterPip) { - desktopData.pipTaskId = taskId - desktopData.pipShouldKeepDesktopActive = true + activeDesk.pipTaskId = taskId + activeDesk.pipShouldKeepDesktopActive = true } else { - desktopData.pipTaskId = - if (desktopData.pipTaskId == taskId) null + activeDesk.pipTaskId = + if (activeDesk.pipTaskId == taskId) null else { logW( - "setTaskInPip: taskId=$taskId did not match saved taskId=${desktopData.pipTaskId}" + "setTaskInPip: taskId=%d did not match saved taskId=%d", + taskId, + activeDesk.pipTaskId, ) - desktopData.pipTaskId + activeDesk.pipTaskId } } notifyVisibleTaskListeners(displayId, getVisibleTaskCount(displayId)) } - /** Returns whether there is a PiP that was entered/minimized from Desktop in this display. */ + /** + * Returns whether there is a PiP that was entered/minimized from Desktop in this display's + * active desk. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun isMinimizedPipPresentInDisplay(displayId: Int): Boolean = - desktopTaskDataByDisplayId.getOrCreate(displayId).pipTaskId != null + desktopData.getActiveDesk(displayId)?.pipTaskId != null - /** Returns whether the given task is the Desktop-entered PiP task in this display. */ + /** + * Returns whether the given task is the Desktop-entered PiP task in this display's active desk. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun isTaskMinimizedPipInDisplay(displayId: Int, taskId: Int): Boolean = - desktopTaskDataByDisplayId.getOrCreate(displayId).pipTaskId == taskId + desktopData.getActiveDesk(displayId)?.pipTaskId == taskId - /** Returns whether Desktop session should be active in this display due to active PiP. */ + /** + * Returns whether a desk should be active in this display due to active PiP. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun shouldDesktopBeActiveForPip(displayId: Int): Boolean = Flags.enableDesktopWindowingPip() && isMinimizedPipPresentInDisplay(displayId) && - desktopTaskDataByDisplayId.getOrCreate(displayId).pipShouldKeepDesktopActive + (desktopData.getActiveDesk(displayId)?.pipShouldKeepDesktopActive ?: false) - /** Saves whether a PiP window should keep Desktop session active in this display. */ + /** + * Saves whether a PiP window should keep Desktop session active in this display. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun setPipShouldKeepDesktopActive(displayId: Int, keepActive: Boolean) { - desktopTaskDataByDisplayId.getOrCreate(displayId).pipShouldKeepDesktopActive = keepActive + desktopData.getActiveDesk(displayId)?.pipShouldKeepDesktopActive = keepActive } - /** Saves callback to handle a pending PiP transition being aborted. */ - fun setOnPipAbortedCallback(callbackIfPipAborted: ((Int, Int) -> Unit)?) { + /** + * Saves callback to handle a pending PiP transition being aborted. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ + fun setOnPipAbortedCallback(callbackIfPipAborted: ((displayId: Int, pipTaskId: Int) -> Unit)?) { onPipAbortedCallback = callbackIfPipAborted } - /** Invokes callback to handle a pending PiP transition with the given task id being aborted. */ + /** + * Invokes callback to handle a pending PiP transition with the given task id being aborted. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun onPipAborted(displayId: Int, pipTaskId: Int) { onPipAbortedCallback?.invoke(displayId, pipTaskId) } - /** Set whether the given task is the full-immersive task in this display. */ + /** + * Set whether the given task is the full-immersive task in this display's active desk. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun setTaskInFullImmersiveState(displayId: Int, taskId: Int, immersive: Boolean) { - val desktopData = desktopTaskDataByDisplayId.getOrCreate(displayId) + val desktopData = desktopData.getActiveDesk(displayId) ?: return if (immersive) { desktopData.fullImmersiveTaskId = taskId } else { @@ -378,25 +522,41 @@ class DesktopRepository( /* Whether the task is in full-immersive state. */ fun isTaskInFullImmersiveState(taskId: Int): Boolean { - return desktopTaskDataSequence().any { taskId == it.fullImmersiveTaskId } + return desksSequence().any { taskId == it.fullImmersiveTaskId } } - /** Returns the task that is currently in immersive mode in this display, or null. */ + /** + * Returns the task that is currently in immersive mode in this display, or null. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun getTaskInFullImmersiveState(displayId: Int): Int? = - desktopTaskDataByDisplayId.getOrCreate(displayId).fullImmersiveTaskId + desktopData.getActiveDesk(displayId)?.fullImmersiveTaskId - /** Sets the top transparent fullscreen task id for a given display. */ + /** + * Sets the top transparent fullscreen task id for a given display's active desk. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun setTopTransparentFullscreenTaskId(displayId: Int, taskId: Int) { - desktopTaskDataByDisplayId.getOrCreate(displayId).topTransparentFullscreenTaskId = taskId + desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId = taskId } - /** Returns the top transparent fullscreen task id for a given display, or null. */ + /** + * Returns the top transparent fullscreen task id for a given display's active desk, or null. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun getTopTransparentFullscreenTaskId(displayId: Int): Int? = - desktopTaskDataByDisplayId.getOrCreate(displayId).topTransparentFullscreenTaskId + desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId - /** Clears the top transparent fullscreen task id info for a given display. */ + /** + * Clears the top transparent fullscreen task id info for a given display's active desk. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun clearTopTransparentFullscreenTaskId(displayId: Int) { - desktopTaskDataByDisplayId.getOrCreate(displayId).topTransparentFullscreenTaskId = null + desktopData.getActiveDesk(displayId)?.topTransparentFullscreenTaskId = null } private fun notifyVisibleTaskListeners(displayId: Int, visibleTasksCount: Int) { @@ -409,22 +569,35 @@ class DesktopRepository( } } - /** Gets number of visible freeform tasks on given [displayId] */ + /** + * Gets number of visible freeform tasks on given [displayId]'s active desk. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun getVisibleTaskCount(displayId: Int): Int = - desktopTaskDataByDisplayId[displayId]?.visibleTasks?.size - ?: 0.also { logD("getVisibleTaskCount=$it") } + (desktopData.getActiveDesk(displayId)?.visibleTasks?.size ?: 0).also { + logD("getVisibleTaskCount=$it") + } /** * Adds task (or moves if it already exists) to the top of the ordered list. * * Unminimizes the task if it is minimized. + * + * TODO: b/389960283 - add explicit [deskId] argument. */ private fun addOrMoveFreeformTaskToTop(displayId: Int, taskId: Int) { - logD("Add or move task to top: display=%d taskId=%d", taskId, displayId) - desktopTaskDataByDisplayId.forEach { _, value -> - value.freeformTasksInZOrder.remove(taskId) - } - desktopTaskDataByDisplayId.getOrCreate(displayId).freeformTasksInZOrder.add(0, taskId) + val activeDesk = + desktopData.getActiveDesk(displayId) + ?: error("Expected a desk to be active in display: $displayId") + logD( + "Add or move task to top: display=%d taskId=%d deskId=%d", + taskId, + displayId, + activeDesk.deskId, + ) + desktopData.forAllDesks { _, desk -> desk.freeformTasksInZOrder.remove(taskId) } + activeDesk.freeformTasksInZOrder.add(0, taskId) // Unminimize the task if it is minimized. unminimizeTask(displayId, taskId) if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) { @@ -432,7 +605,11 @@ class DesktopRepository( } } - /** Minimizes the task for [taskId] and [displayId] */ + /** + * Minimizes the task for [taskId] and [displayId]'s active display. + * + * TODO: b/389960283 - add explicit [deskId] argument. + */ fun minimizeTask(displayId: Int, taskId: Int) { if (displayId == INVALID_DISPLAY) { // When a task vanishes it doesn't have a displayId. Find the display of the task and @@ -441,7 +618,8 @@ class DesktopRepository( ?: logW("Minimize task: No display id found for task: taskId=%d", taskId) } else { logD("Minimize Task: display=%d, task=%d", displayId, taskId) - desktopTaskDataByDisplayId.getOrCreate(displayId).minimizedTasks.add(taskId) + desktopData.getActiveDesk(displayId)?.minimizedTasks?.add(taskId) + ?: logD("Minimize task: No active desk found for task: taskId=%d", taskId) } updateTask(displayId, taskId, isVisible = false) if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) { @@ -449,26 +627,42 @@ class DesktopRepository( } } - /** Unminimizes the task for [taskId] and [displayId] */ + /** + * Unminimizes the task for [taskId] and [displayId]. + * + * TODO: b/389960283 - consider adding an explicit [deskId] argument. + */ fun unminimizeTask(displayId: Int, taskId: Int) { logD("Unminimize Task: display=%d, task=%d", displayId, taskId) - desktopTaskDataByDisplayId[displayId]?.minimizedTasks?.remove(taskId) - ?: logW("Unminimize Task: display=%d, task=%d, no task data", displayId, taskId) + var removed = false + desktopData.forAllDesks(displayId) { desk -> + if (desk.minimizedTasks.remove(taskId)) { + removed = true + } + } + if (!removed) { + logW("Unminimize Task: display=%d, task=%d, no task data", displayId, taskId) + } } private fun getDisplayIdForTask(taskId: Int): Int? { - desktopTaskDataByDisplayId.forEach { displayId, data -> - if (taskId in data.freeformTasksInZOrder) { - return displayId + var displayForTask: Int? = null + desktopData.forAllDesks { displayId, desk -> + if (taskId in desk.freeformTasksInZOrder) { + displayForTask = displayId } } - logW("No display id found for task: taskId=%d", taskId) - return null + if (displayForTask == null) { + logW("No display id found for task: taskId=%d", taskId) + } + return displayForTask } /** * Removes [taskId] from the respective display. If [INVALID_DISPLAY], the original display id * will be looked up from the task id. + * + * TODO: b/389960283 - consider adding an explicit [deskId] argument. */ fun removeTask(displayId: Int, taskId: Int) { logD("Removes freeform task: taskId=%d", taskId) @@ -483,13 +677,17 @@ class DesktopRepository( /** Removes given task from a valid [displayId] and updates the repository state. */ private fun removeTaskFromDisplay(displayId: Int, taskId: Int) { logD("Removes freeform task: taskId=%d, displayId=%d", taskId, displayId) - desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.remove(taskId) + desktopData.forAllDesks(displayId) { desk -> + if (desk.freeformTasksInZOrder.remove(taskId)) { + logD( + "Remaining freeform tasks in desk: %d, tasks: %s", + desk.deskId, + desk.freeformTasksInZOrder.toDumpString(), + ) + } + } boundsBeforeMaximizeByTaskId.remove(taskId) boundsBeforeFullImmersiveByTaskId.remove(taskId) - logD( - "Remaining freeform tasks: %s", - desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.toDumpString(), - ) // Remove task from unminimized task if it is minimized. unminimizeTask(displayId, taskId) // Mark task as not in immersive if it was immersive. @@ -502,15 +700,18 @@ class DesktopRepository( } /** - * Removes the desktop for the given [displayId] and returns the active tasks on that desktop. + * Removes the active desk for the given [displayId] and returns the active tasks on that desk. + * + * TODO: b/389960283 - add explicit [deskId] argument. */ - fun removeDesktop(displayId: Int): ArraySet<Int> { - if (!desktopTaskDataByDisplayId.contains(displayId)) { - logW("Could not find desktop to remove: displayId=%d", displayId) + fun removeDesk(displayId: Int): ArraySet<Int> { + val desk = desktopData.getActiveDesk(displayId) + if (desk == null) { + logW("Could not find desk to remove: displayId=%d", displayId) return ArraySet() } - val activeTasks = ArraySet(desktopTaskDataByDisplayId[displayId].activeTasks) - desktopTaskDataByDisplayId[displayId].clear() + val activeTasks = ArraySet(desk.activeTasks) + desktopData.remove(desk.deskId) return activeTasks } @@ -564,19 +765,20 @@ class DesktopRepository( fun saveBoundsBeforeFullImmersive(taskId: Int, bounds: Rect) = boundsBeforeFullImmersiveByTaskId.set(taskId, Rect(bounds)) + /** TODO: b/389960283 - consider updating only the changing desks. */ private fun updatePersistentRepository(displayId: Int) { - // Create a deep copy of the data - desktopTaskDataByDisplayId[displayId]?.deepCopy()?.let { desktopTaskDataByDisplayIdCopy -> - mainCoroutineScope.launch { + val desks = desktopData.desksSequence(displayId).map { desk -> desk.deepCopy() }.toList() + mainCoroutineScope.launch { + desks.forEach { desk -> try { persistentRepository.addOrUpdateDesktop( - // Use display id as desktop id for now since only once desktop per display + // Use display id as desk id for now since only once desk per display // is supported. userId = userId, - desktopId = displayId, - visibleTasks = desktopTaskDataByDisplayIdCopy.visibleTasks, - minimizedTasks = desktopTaskDataByDisplayIdCopy.minimizedTasks, - freeformTasksInZOrder = desktopTaskDataByDisplayIdCopy.freeformTasksInZOrder, + desktopId = desk.deskId, + visibleTasks = desk.visibleTasks, + minimizedTasks = desk.minimizedTasks, + freeformTasksInZOrder = desk.freeformTasksInZOrder, ) } catch (exception: Exception) { logE( @@ -598,20 +800,27 @@ class DesktopRepository( private fun dumpDesktopTaskData(pw: PrintWriter, prefix: String) { val innerPrefix = "$prefix " - desktopTaskDataByDisplayId.forEach { displayId, data -> - pw.println("${prefix}Display $displayId:") - pw.println("${innerPrefix}activeTasks=${data.activeTasks.toDumpString()}") - pw.println("${innerPrefix}visibleTasks=${data.visibleTasks.toDumpString()}") - pw.println( - "${innerPrefix}freeformTasksInZOrder=${data.freeformTasksInZOrder.toDumpString()}" - ) - pw.println("${innerPrefix}minimizedTasks=${data.minimizedTasks.toDumpString()}") - pw.println("${innerPrefix}fullImmersiveTaskId=${data.fullImmersiveTaskId}") - pw.println( - "${innerPrefix}topTransparentFullscreenTaskId=" + - "${data.topTransparentFullscreenTaskId}" - ) - } + desktopData + .desksSequence() + .groupBy { it.displayId } + .forEach { (displayId, desks) -> + pw.println("${prefix}Display #$displayId:") + desks.forEach { desk -> + pw.println("${innerPrefix}Desk #${desk.deskId}:") + pw.print("$innerPrefix activeTasks=") + pw.println(desk.activeTasks.toDumpString()) + pw.print("$innerPrefix visibleTasks=") + pw.println(desk.visibleTasks.toDumpString()) + pw.print("$innerPrefix freeformTasksInZOrder=") + pw.println(desk.freeformTasksInZOrder.toDumpString()) + pw.print("$innerPrefix minimizedTasks=") + pw.println(desk.minimizedTasks.toDumpString()) + pw.print("$innerPrefix fullImmersiveTaskId=") + pw.println(desk.fullImmersiveTaskId) + pw.print("$innerPrefix topTransparentFullscreenTaskId=") + pw.println(desk.topTransparentFullscreenTaskId) + } + } } /** Listens to changes for active tasks in desktop mode. */ @@ -624,6 +833,227 @@ class DesktopRepository( fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {} } + /** An interface for the desktop hierarchy's data managed by this repository. */ + private interface DesktopData { + /** + * Returns the existing desk or creates a new entry if needed. + * + * TODO: 389787966 - consider removing this as it cannot be assumed a desk can be created in + * all devices / form-factors. + */ + fun getOrCreateDesk(displayId: Int, deskId: Int): Desk + + /** Returns the desk with the given id, or null if it does not exist. */ + fun getDesk(deskId: Int): Desk? + + /** Returns the active desk in this diplay, or null if none are active. */ + fun getActiveDesk(displayId: Int): Desk? + + /** Sets the given desk as the active desk in the given display. */ + fun setActiveDesk(displayId: Int, deskId: Int) + + /** + * Returns the default desk in the given display. Useful when the system wants to activate a + * desk but doesn't care about which one it activates (e.g. when putting a window into a + * desk using the App Handle). May return null if the display does not support desks. + * + * TODO: 389787966 - consider removing or renaming. In practice, this is needed for + * soon-to-be deprecated IDesktopMode APIs, adb commands or entry-points into the only + * desk (single-desk devices) or the most-recent desk (multi-desk devices). + */ + fun getDefaultDesk(displayId: Int): Desk? + + /** Returns all the active desks of all displays. */ + fun getAllActiveDesks(): Set<Desk> + + /** Returns the number of desks in the given display. */ + fun getNumberOfDesks(displayId: Int): Int + + /** Applies a function to all desks. */ + fun forAllDesks(consumer: (Desk) -> Unit) + + /** Applies a function to all desks. */ + fun forAllDesks(consumer: (displayId: Int, Desk) -> Unit) + + /** Applies a function to all desks under the given display. */ + fun forAllDesks(displayId: Int, consumer: (Desk) -> Unit) + + /** Returns a sequence of all desks. */ + fun desksSequence(): Sequence<Desk> + + /** Returns a sequence of all desks under the given display. */ + fun desksSequence(displayId: Int): Sequence<Desk> + + /** Remove an existing desk if it exists. */ + fun remove(deskId: Int) + + /** Returns the id of the display where the given desk is located. */ + fun getDisplayForDesk(deskId: Int): Int + } + + /** + * A [DesktopData] implementation that only supports one desk per display. + * + * Internally, it reuses the displayId as that display's single desk's id. + */ + private class SingleDesktopData : DesktopData { + private val deskByDisplayId = + object : SparseArray<Desk>() { + /** Gets [Desk] for existing [displayId] or creates a new one. */ + fun getOrCreate(displayId: Int): Desk = + this[displayId] + ?: Desk(deskId = displayId, displayId = displayId).also { + this[displayId] = it + } + } + + override fun getOrCreateDesk(displayId: Int, deskId: Int): Desk { + check(displayId == deskId) + return deskByDisplayId.getOrCreate(displayId) + } + + override fun getDesk(deskId: Int): Desk = getOrCreateDesk(deskId, deskId) + + override fun getActiveDesk(displayId: Int): Desk { + // TODO: 389787966 - consider migrating to an "active" state instead of checking the + // number of visible active tasks, PIP in desktop, and empty desktop logic. In + // practice, existing single-desktop devices are ok with this function returning the + // only desktop, even if it's not active. + return deskByDisplayId.getOrCreate(displayId) + } + + override fun setActiveDesk(displayId: Int, deskId: Int) { + // No-op, in single-desk setups, which desktop is "active" is determined by the + // existence of visible desktop windows, among other factors. + } + + override fun getDefaultDesk(displayId: Int): Desk = getOrCreateDesk(displayId, displayId) + + override fun getAllActiveDesks(): Set<Desk> = + deskByDisplayId.valueIterator().asSequence().toSet() + + override fun getNumberOfDesks(displayId: Int): Int = 1 + + override fun forAllDesks(consumer: (Desk) -> Unit) { + deskByDisplayId.forEach { _, desk -> consumer(desk) } + } + + override fun forAllDesks(consumer: (Int, Desk) -> Unit) { + deskByDisplayId.forEach { displayId, desk -> consumer(displayId, desk) } + } + + override fun forAllDesks(displayId: Int, consumer: (Desk) -> Unit) { + consumer(getOrCreateDesk(displayId, displayId)) + } + + override fun desksSequence(): Sequence<Desk> = deskByDisplayId.valueIterator().asSequence() + + override fun desksSequence(displayId: Int): Sequence<Desk> = + deskByDisplayId[displayId]?.let { sequenceOf(it) } ?: emptySequence() + + override fun remove(deskId: Int) { + deskByDisplayId[deskId]?.clear() + } + + override fun getDisplayForDesk(deskId: Int): Int = deskId + } + + /** A [DesktopData] implementation that supports multiple desks. */ + private class MultiDesktopData : DesktopData { + private val desktopDisplays = SparseArray<DesktopDisplay>() + + override fun getOrCreateDesk(displayId: Int, deskId: Int): Desk { + val display = + desktopDisplays[displayId] + ?: DesktopDisplay(displayId).also { desktopDisplays[displayId] = it } + val desk = + display.orderedDesks.find { desk -> desk.deskId == deskId } + ?: Desk(deskId = deskId, displayId = displayId).also { + display.orderedDesks.add(it) + } + return desk + } + + override fun getDesk(deskId: Int): Desk? { + desktopDisplays.forEach { _, display -> + val desk = display.orderedDesks.find { desk -> desk.deskId == deskId } + if (desk != null) { + return desk + } + } + return null + } + + override fun getActiveDesk(displayId: Int): Desk? { + val display = desktopDisplays[displayId] ?: return null + if (display.activeDeskId == null) return null + return display.orderedDesks.find { it.deskId == display.activeDeskId } + } + + override fun setActiveDesk(displayId: Int, deskId: Int) { + val display = + desktopDisplays[displayId] ?: error("Expected display#$displayId to exist") + val desk = display.orderedDesks.single { it.deskId == deskId } + display.activeDeskId = desk.deskId + } + + override fun getDefaultDesk(displayId: Int): Desk? { + val display = desktopDisplays[displayId] ?: return null + return display.orderedDesks.firstOrNull() + } + + override fun getAllActiveDesks(): Set<Desk> { + return desktopDisplays + .valueIterator() + .asSequence() + .filter { display -> display.activeDeskId != null } + .map { display -> + display.orderedDesks.single { it.deskId == display.activeDeskId } + } + .toSet() + } + + override fun getNumberOfDesks(displayId: Int): Int = + desktopDisplays[displayId]?.orderedDesks?.size ?: 0 + + override fun forAllDesks(consumer: (Desk) -> Unit) { + desktopDisplays.forEach { _, display -> display.orderedDesks.forEach { consumer(it) } } + } + + override fun forAllDesks(consumer: (Int, Desk) -> Unit) { + desktopDisplays.forEach { _, display -> + display.orderedDesks.forEach { consumer(display.displayId, it) } + } + } + + override fun forAllDesks(displayId: Int, consumer: (Desk) -> Unit) { + desktopDisplays + .valueIterator() + .asSequence() + .filter { display -> display.displayId == displayId } + .flatMap { display -> display.orderedDesks.asSequence() } + .forEach { desk -> consumer(desk) } + } + + override fun desksSequence(): Sequence<Desk> = + desktopDisplays.valueIterator().asSequence().flatMap { display -> + display.orderedDesks.asSequence() + } + + override fun desksSequence(displayId: Int): Sequence<Desk> = + desktopDisplays[displayId]?.orderedDesks?.asSequence() ?: emptySequence() + + override fun remove(deskId: Int) { + desktopDisplays.forEach { _, display -> + display.orderedDesks.removeIf { it.deskId == deskId } + } + } + + override fun getDisplayForDesk(deskId: Int): Int = + getAllActiveDesks().find { it.deskId == deskId }?.displayId + ?: error("Display for desk=$deskId not found") + } + private fun logD(msg: String, vararg arguments: Any?) { ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) } 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 91e06300f1b0..172410d0482c 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 @@ -87,6 +87,7 @@ import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing import com.android.wm.shell.compatui.isTransparentTask import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState @@ -466,7 +467,9 @@ class DesktopTasksController( desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted( FREEFORM_ANIMATION_DURATION ) - taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) } + taskIdToMinimize?.let { + addPendingMinimizeTransition(transition, it, MinimizeReason.TASK_LIMIT) + } exitResult.asExit()?.runOnTransitionStart?.invoke(transition) return true } @@ -512,7 +515,9 @@ class DesktopTasksController( desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted( FREEFORM_ANIMATION_DURATION ) - taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) } + taskIdToMinimize?.let { + addPendingMinimizeTransition(transition, it, MinimizeReason.TASK_LIMIT) + } exitResult.asExit()?.runOnTransitionStart?.invoke(transition) } @@ -573,7 +578,9 @@ class DesktopTasksController( DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS.toInt() ) transition?.let { - taskIdToMinimize?.let { taskId -> addPendingMinimizeTransition(it, taskId) } + taskIdToMinimize?.let { taskId -> + addPendingMinimizeTransition(it, taskId, MinimizeReason.TASK_LIMIT) + } exitResult.asExit()?.runOnTransitionStart?.invoke(transition) } } @@ -622,7 +629,7 @@ class DesktopTasksController( ?.runOnTransitionStart } - fun minimizeTask(taskInfo: RunningTaskInfo) { + fun minimizeTask(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) { val wct = WindowContainerTransaction() val isMinimizingToPip = taskInfo.pictureInPictureParams?.isAutoEnterEnabled() ?: false @@ -642,16 +649,16 @@ class DesktopTasksController( freeformTaskTransitionStarter.startPipTransition(wct) taskRepository.setTaskInPip(taskInfo.displayId, taskInfo.taskId, enterPip = true) taskRepository.setOnPipAbortedCallback { displayId, taskId -> - minimizeTaskInner(shellTaskOrganizer.getRunningTaskInfo(taskId)!!) + minimizeTaskInner(shellTaskOrganizer.getRunningTaskInfo(taskId)!!, minimizeReason) taskRepository.setTaskInPip(displayId, taskId, enterPip = false) } return } - minimizeTaskInner(taskInfo) + minimizeTaskInner(taskInfo, minimizeReason) } - private fun minimizeTaskInner(taskInfo: RunningTaskInfo) { + private fun minimizeTaskInner(taskInfo: RunningTaskInfo, minimizeReason: MinimizeReason) { val taskId = taskInfo.taskId val displayId = taskInfo.displayId val wct = WindowContainerTransaction() @@ -671,6 +678,7 @@ class DesktopTasksController( transition = transition, displayId = displayId, taskId = taskId, + minimizeReason = minimizeReason, ) } exitResult.asExit()?.runOnTransitionStart?.invoke(transition) @@ -826,7 +834,7 @@ class DesktopTasksController( minimizingTaskId = taskIdToMinimize, exitingImmersiveTask = exitImmersiveResult.asExit()?.exitingTask, ) - taskIdToMinimize?.let { addPendingMinimizeTransition(t, it) } + taskIdToMinimize?.let { addPendingMinimizeTransition(t, it, MinimizeReason.TASK_LIMIT) } exitImmersiveResult.asExit()?.runOnTransitionStart?.invoke(t) return t } @@ -846,7 +854,7 @@ class DesktopTasksController( ) val t = transitions.startTransition(transitionType, wct, remoteTransitionHandler) remoteTransitionHandler.setTransition(t) - taskIdToMinimize.let { addPendingMinimizeTransition(t, it) } + taskIdToMinimize.let { addPendingMinimizeTransition(t, it, MinimizeReason.TASK_LIMIT) } exitImmersiveResult.asExit()?.runOnTransitionStart?.invoke(t) return t } @@ -1898,7 +1906,7 @@ class DesktopTasksController( val taskIdToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId) addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize) if (taskIdToMinimize != null) { - addPendingMinimizeTransition(transition, taskIdToMinimize) + addPendingMinimizeTransition(transition, taskIdToMinimize, MinimizeReason.TASK_LIMIT) return wct } if (!wct.isEmpty) { @@ -1932,7 +1940,9 @@ class DesktopTasksController( // Desktop Mode is already showing and we're launching a new Task - we might need to // minimize another Task. val taskIdToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId) - taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) } + taskIdToMinimize?.let { + addPendingMinimizeTransition(transition, it, MinimizeReason.TASK_LIMIT) + } addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize) desktopImmersiveController.exitImmersiveIfApplicable( transition, @@ -2180,13 +2190,18 @@ class DesktopTasksController( .addAndGetMinimizeTaskChanges(displayId, wct, newTaskId, launchingNewIntent) } - private fun addPendingMinimizeTransition(transition: IBinder, taskIdToMinimize: Int) { + private fun addPendingMinimizeTransition( + transition: IBinder, + taskIdToMinimize: Int, + minimizeReason: MinimizeReason, + ) { val taskToMinimize = shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize) desktopTasksLimiter.ifPresent { it.addPendingMinimizeChange( transition = transition, displayId = taskToMinimize?.displayId ?: DEFAULT_DISPLAY, taskId = taskIdToMinimize, + minimizeReason = minimizeReason, ) } } @@ -2216,7 +2231,7 @@ class DesktopTasksController( fun removeDesktop(displayId: Int) { if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return - val tasksToRemove = taskRepository.removeDesktop(displayId) + val tasksToRemove = taskRepository.removeDesk(displayId) val wct = WindowContainerTransaction() tasksToRemove.forEach { val task = shellTaskOrganizer.getRunningTaskInfo(it) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt index e4a28e9efe60..204b39645248 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt @@ -30,6 +30,7 @@ import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_MINIMIZE_WINDOW import com.android.internal.jank.InteractionJankMonitor import com.android.internal.protolog.ProtoLog import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.sysui.UserChangeListener @@ -67,12 +68,21 @@ class DesktopTasksLimiter( logV("Starting limiter with a maximum of %d tasks", maxTasksLimit) } - private data class TaskDetails( + data class TaskDetails( val displayId: Int, val taskId: Int, - var transitionInfo: TransitionInfo?, + var transitionInfo: TransitionInfo? = null, + val minimizeReason: MinimizeReason? = null, ) + /** + * Returns the task being minimized in the given transition if that transition is a pending or + * active minimize transition. + */ + fun getMinimizingTask(transition: IBinder): TaskDetails? { + return minimizeTransitionObserver.getMinimizingTask(transition) + } + // TODO(b/333018485): replace this observer when implementing the minimize-animation private inner class MinimizeTransitionObserver : TransitionObserver { private val pendingTransitionTokensAndTasks = mutableMapOf<IBinder, TaskDetails>() @@ -82,6 +92,11 @@ class DesktopTasksLimiter( pendingTransitionTokensAndTasks[transition] = taskDetails } + fun getMinimizingTask(transition: IBinder): TaskDetails? { + return pendingTransitionTokensAndTasks[transition] + ?: activeTransitionTokensAndTasks[transition] + } + override fun onTransitionReady( transition: IBinder, info: TransitionInfo, @@ -89,6 +104,14 @@ class DesktopTasksLimiter( finishTransaction: SurfaceControl.Transaction, ) { val taskRepository = desktopUserRepositories.current + handleMinimizeTransition(taskRepository, transition, info) + } + + private fun handleMinimizeTransition( + taskRepository: DesktopRepository, + transition: IBinder, + info: TransitionInfo, + ) { val taskToMinimize = pendingTransitionTokensAndTasks.remove(transition) ?: return if (!taskRepository.isActiveTask(taskToMinimize.taskId)) return if (!isTaskReadyForMinimize(info, taskToMinimize)) { @@ -241,10 +264,15 @@ class DesktopTasksLimiter( * Add a pending minimize transition change to update the list of minimized apps once the * transition goes through. */ - fun addPendingMinimizeChange(transition: IBinder, displayId: Int, taskId: Int) { + fun addPendingMinimizeChange( + transition: IBinder, + displayId: Int, + taskId: Int, + minimizeReason: MinimizeReason, + ) { minimizeTransitionObserver.addPendingTransitionToken( transition, - TaskDetails(displayId, taskId, transitionInfo = null), + TaskDetails(displayId, taskId, transitionInfo = null, minimizeReason = minimizeReason), ) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index aeccd86e122c..36eaebdf4fff 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -1148,9 +1148,12 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, change, layer, info, t, mLeashMap); appearedTargets[nextTargetIdx++] = target; // reparent into the original `mInfo` since that's where we are animating. - final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo); + final TransitionInfo.Root root = TransitionUtil.getRootFor(change, mInfo); final boolean wasClosing = closingIdx >= 0; - t.reparent(target.leash, mInfo.getRoot(rootIdx).getLeash()); + t.reparent(target.leash, root.getLeash()); + t.setPosition(target.leash, + change.getStartAbsBounds().left - root.getOffset().x, + change.getStartAbsBounds().top - root.getOffset().y); t.setLayer(target.leash, layer); if (wasClosing) { // App was previously visible and is closing 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 d6d393f2500c..eb3a698fb58e 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 @@ -34,6 +34,7 @@ import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_MODE_APP_HAND import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions; import static com.android.wm.shell.compatui.AppCompatUtils.isTopActivityExemptFromDesktopWindowing; import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod; +import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason; import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger; import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR; import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR; @@ -980,7 +981,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, ToggleTaskSizeInteraction.AmbiguousSource.HEADER_BUTTON, mMotionEvent); } } else if (id == R.id.minimize_window) { - mDesktopTasksController.minimizeTask(decoration.mTaskInfo); + mDesktopTasksController.minimizeTask( + decoration.mTaskInfo, MinimizeReason.MINIMIZE_BUTTON); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt index 413e7bc5d1d6..016e04039b12 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt @@ -46,6 +46,7 @@ import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction import com.android.wm.shell.shared.desktopmode.DesktopModeStatus @@ -294,7 +295,7 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() { testExecutor.flushAll() assertThat(result).isTrue() - verify(desktopTasksController).minimizeTask(task) + verify(desktopTasksController).minimizeTask(task, MinimizeReason.KEY_GESTURE) } private fun setUpFreeformTask( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt index 4317143aebfe..a9ebcef9bd98 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt @@ -42,6 +42,7 @@ import android.window.WindowContainerToken import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.modules.utils.testing.ExtendedMockitoRule +import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason @@ -62,6 +63,7 @@ import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.TransitionInfoBuilder import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TRANSIT_MINIMIZE +import java.util.Optional import kotlin.test.assertFalse import kotlin.test.assertTrue import org.junit.Before @@ -69,6 +71,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor +import org.mockito.Mockito.`when` import org.mockito.kotlin.any import org.mockito.kotlin.eq import org.mockito.kotlin.mock @@ -102,6 +105,8 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { private val mockShellInit = mock<ShellInit>() private val transitions = mock<Transitions>() private val context = mock<Context>() + private val shellTaskOrganizer = mock<ShellTaskOrganizer>() + private val desktopTasksLimiter = mock<DesktopTasksLimiter>() private lateinit var transitionObserver: DesktopModeLoggerTransitionObserver private lateinit var shellInit: ShellInit @@ -119,6 +124,8 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { mockShellInit, transitions, desktopModeEventLogger, + Optional.of(desktopTasksLimiter), + shellTaskOrganizer, ) val initRunnableCaptor = ArgumentCaptor.forClass(Runnable::class.java) verify(mockShellInit) @@ -755,6 +762,39 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { verify(desktopModeEventLogger, never()).logSessionExit(any()) } + @Test + fun onTransitionReady_taskIsBeingMinimized_logsTaskMinimized() { + transitionObserver.isSessionActive = true + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM, id = 1)) + val taskInfo2 = createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2) + transitionObserver.addTaskInfosToCachedMap(taskInfo2) + val transitionInfo = + TransitionInfoBuilder(TRANSIT_TO_BACK, 0) + .addChange(createChange(TRANSIT_TO_BACK, taskInfo2)) + .build() + `when`(desktopTasksLimiter.getMinimizingTask(any())) + .thenReturn( + DesktopTasksLimiter.TaskDetails( + taskInfo2.displayId, + taskInfo2.taskId, + minimizeReason = MinimizeReason.TASK_LIMIT, + ) + ) + + callOnTransitionReady(transitionInfo) + + verify(desktopModeEventLogger, times(1)) + .logTaskRemoved( + eq( + DEFAULT_TASK_UPDATE.copy( + instanceId = 2, + visibleTaskCount = 1, + minimizeReason = MinimizeReason.TASK_LIMIT, + ) + ) + ) + } + /** Simulate calling the onTransitionReady() method */ private fun callOnTransitionReady(transitionInfo: TransitionInfo) { val transition = mock<IBinder>() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt index 6003a219d4db..8d73f3f59afd 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt @@ -1046,14 +1046,14 @@ class DesktopRepositoryTest : ShellTestCase() { } @Test - fun removeDesktop_multipleTasks_removesAll() { + fun removeDesk_multipleTasks_removesAll() { // The front-most task will be the one added last through `addTask`. repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 3, isVisible = true) repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 2, isVisible = true) repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 1, isVisible = true) repo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = 2) - val tasksBeforeRemoval = repo.removeDesktop(displayId = DEFAULT_DISPLAY) + val tasksBeforeRemoval = repo.removeDesk(displayId = DEFAULT_DISPLAY) assertThat(tasksBeforeRemoval).containsExactly(1, 2, 3).inOrder() assertThat(repo.getActiveTasks(displayId = DEFAULT_DISPLAY)).isEmpty() 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 d13ff79b9518..6c4f043a4f39 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 @@ -102,6 +102,7 @@ import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopImmersiveController.ExitResult import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger import com.android.wm.shell.desktopmode.DesktopTasksController.DesktopModeEntryExitTransitionListener import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition @@ -2162,7 +2163,7 @@ class DesktopTasksControllerTest : ShellTestCase() { whenever(transitions.dispatchRequest(any(), any(), anyOrNull())) .thenReturn(android.util.Pair(handler, WindowContainerTransaction())) - controller.minimizeTask(pipTask) + controller.minimizeTask(pipTask, MinimizeReason.MINIMIZE_BUTTON) verifyExitDesktopWCTNotExecuted() taskRepository.setTaskInPip(DEFAULT_DISPLAY, pipTask.taskId, enterPip = false) @@ -2182,7 +2183,7 @@ class DesktopTasksControllerTest : ShellTestCase() { whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) .thenReturn(transition) - controller.minimizeTask(task) + controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) @@ -2198,7 +2199,7 @@ class DesktopTasksControllerTest : ShellTestCase() { whenever(transitions.dispatchRequest(any(), any(), anyOrNull())) .thenReturn(android.util.Pair(handler, WindowContainerTransaction())) - controller.minimizeTask(task) + controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) verify(freeformTaskTransitionStarter).startPipTransition(any()) verify(freeformTaskTransitionStarter, never()).startMinimizedModeTransition(any()) @@ -2210,7 +2211,7 @@ class DesktopTasksControllerTest : ShellTestCase() { whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) .thenReturn(Binder()) - controller.minimizeTask(task) + controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) verify(freeformTaskTransitionStarter).startMinimizedModeTransition(any()) verify(freeformTaskTransitionStarter, never()).startPipTransition(any()) @@ -2223,7 +2224,7 @@ class DesktopTasksControllerTest : ShellTestCase() { whenever(transitions.dispatchRequest(any(), any(), anyOrNull())) .thenReturn(android.util.Pair(handler, WindowContainerTransaction())) - controller.minimizeTask(task) + controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) verify(freeformTaskTransitionStarter).startPipTransition(captor.capture()) @@ -2239,7 +2240,7 @@ class DesktopTasksControllerTest : ShellTestCase() { whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) .thenReturn(transition) - controller.minimizeTask(task) + controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) @@ -2255,7 +2256,7 @@ class DesktopTasksControllerTest : ShellTestCase() { .thenReturn(transition) // The only active task is being minimized. - controller.minimizeTask(task) + controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) @@ -2272,7 +2273,7 @@ class DesktopTasksControllerTest : ShellTestCase() { taskRepository.minimizeTask(DEFAULT_DISPLAY, task.taskId) // The only active task is already minimized. - controller.minimizeTask(task) + controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) @@ -2289,7 +2290,7 @@ class DesktopTasksControllerTest : ShellTestCase() { whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) .thenReturn(transition) - controller.minimizeTask(task1) + controller.minimizeTask(task1, MinimizeReason.MINIMIZE_BUTTON) val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) @@ -2309,7 +2310,7 @@ class DesktopTasksControllerTest : ShellTestCase() { taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId) // task1 is the only visible task as task2 is minimized. - controller.minimizeTask(task1) + controller.minimizeTask(task1, MinimizeReason.MINIMIZE_BUTTON) // Adds remove wallpaper operation val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture()) @@ -2324,7 +2325,7 @@ class DesktopTasksControllerTest : ShellTestCase() { whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) .thenReturn(transition) - controller.minimizeTask(task) + controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) verify(mMockDesktopImmersiveController).exitImmersiveIfApplicable(any(), eq(task), any()) } @@ -2341,7 +2342,7 @@ class DesktopTasksControllerTest : ShellTestCase() { ExitResult.Exit(exitingTask = task.taskId, runOnTransitionStart = runOnTransit) ) - controller.minimizeTask(task) + controller.minimizeTask(task, MinimizeReason.MINIMIZE_BUTTON) assertThat(runOnTransit.invocations).isEqualTo(1) assertThat(runOnTransit.lastInvoked).isEqualTo(transition) @@ -3285,7 +3286,7 @@ class DesktopTasksControllerTest : ShellTestCase() { whenever(transitions.dispatchRequest(any(), any(), anyOrNull())) .thenReturn(android.util.Pair(handler, WindowContainerTransaction())) - controller.minimizeTask(pipTask) + controller.minimizeTask(pipTask, MinimizeReason.MINIMIZE_BUTTON) verifyExitDesktopWCTNotExecuted() freeformTask.isFocused = true diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt index c8214b3838e2..acfe1e9fd5a2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt @@ -20,6 +20,7 @@ import android.app.ActivityManager.RunningTaskInfo import android.graphics.Rect import android.os.Binder import android.os.Handler +import android.os.IBinder import android.os.UserManager import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags @@ -43,6 +44,7 @@ import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGAT import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer @@ -180,7 +182,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { val task = setUpFreeformTask() markTaskHidden(task) - desktopTasksLimiter.addPendingMinimizeChange(Binder(), displayId = 1, taskId = task.taskId) + addPendingMinimizeChange(Binder(), displayId = 1, taskId = task.taskId) assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isFalse() } @@ -208,11 +210,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { val taskTransition = Binder() val task = setUpFreeformTask() markTaskHidden(task) - desktopTasksLimiter.addPendingMinimizeChange( - pendingTransition, - displayId = DEFAULT_DISPLAY, - taskId = task.taskId, - ) + addPendingMinimizeChange(pendingTransition, taskId = task.taskId) desktopTasksLimiter .getTransitionObserver() @@ -231,11 +229,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { val transition = Binder() val task = setUpFreeformTask() markTaskVisible(task) - desktopTasksLimiter.addPendingMinimizeChange( - transition, - displayId = DEFAULT_DISPLAY, - taskId = task.taskId, - ) + addPendingMinimizeChange(transition, taskId = task.taskId) desktopTasksLimiter .getTransitionObserver() @@ -254,11 +248,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { val transition = Binder() val task = setUpFreeformTask() markTaskHidden(task) - desktopTasksLimiter.addPendingMinimizeChange( - transition, - displayId = DEFAULT_DISPLAY, - taskId = task.taskId, - ) + addPendingMinimizeChange(transition, taskId = task.taskId) desktopTasksLimiter .getTransitionObserver() @@ -276,11 +266,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { fun onTransitionReady_pendingTransition_changeTaskToBack_taskIsMinimized() { val transition = Binder() val task = setUpFreeformTask() - desktopTasksLimiter.addPendingMinimizeChange( - transition, - displayId = DEFAULT_DISPLAY, - taskId = task.taskId, - ) + addPendingMinimizeChange(transition, taskId = task.taskId) desktopTasksLimiter .getTransitionObserver() @@ -299,11 +285,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { val bounds = Rect(0, 0, 200, 200) val transition = Binder() val task = setUpFreeformTask() - desktopTasksLimiter.addPendingMinimizeChange( - transition, - displayId = DEFAULT_DISPLAY, - taskId = task.taskId, - ) + addPendingMinimizeChange(transition, taskId = task.taskId) val change = TransitionInfo.Change(task.token, mock(SurfaceControl::class.java)).apply { @@ -330,11 +312,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { val mergedTransition = Binder() val newTransition = Binder() val task = setUpFreeformTask() - desktopTasksLimiter.addPendingMinimizeChange( - mergedTransition, - displayId = DEFAULT_DISPLAY, - taskId = task.taskId, - ) + addPendingMinimizeChange(mergedTransition, taskId = task.taskId) desktopTasksLimiter .getTransitionObserver() .onTransitionMerged(mergedTransition, newTransition) @@ -541,11 +519,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { (1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() } val transition = Binder() val task = setUpFreeformTask() - desktopTasksLimiter.addPendingMinimizeChange( - transition, - displayId = DEFAULT_DISPLAY, - taskId = task.taskId, - ) + addPendingMinimizeChange(transition, taskId = task.taskId) desktopTasksLimiter .getTransitionObserver() @@ -573,11 +547,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { (1..<MAX_TASK_LIMIT).forEach { _ -> setUpFreeformTask() } val transition = Binder() val task = setUpFreeformTask() - desktopTasksLimiter.addPendingMinimizeChange( - transition, - displayId = DEFAULT_DISPLAY, - taskId = task.taskId, - ) + addPendingMinimizeChange(transition, taskId = task.taskId) desktopTasksLimiter .getTransitionObserver() @@ -606,11 +576,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { val mergedTransition = Binder() val newTransition = Binder() val task = setUpFreeformTask() - desktopTasksLimiter.addPendingMinimizeChange( - mergedTransition, - displayId = DEFAULT_DISPLAY, - taskId = task.taskId, - ) + addPendingMinimizeChange(mergedTransition, taskId = task.taskId) desktopTasksLimiter .getTransitionObserver() @@ -633,6 +599,60 @@ class DesktopTasksLimiterTest : ShellTestCase() { verify(interactionJankMonitor).end(eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)) } + @Test + fun getMinimizingTask_noPendingTransition_returnsNull() { + val transition = Binder() + + assertThat(desktopTasksLimiter.getMinimizingTask(transition)).isNull() + } + + @Test + fun getMinimizingTask_pendingTaskTransition_returnsTask() { + val transition = Binder() + val task = setUpFreeformTask() + addPendingMinimizeChange( + transition, + taskId = task.taskId, + minimizeReason = MinimizeReason.TASK_LIMIT, + ) + + assertThat(desktopTasksLimiter.getMinimizingTask(transition)) + .isEqualTo( + createTaskDetails(taskId = task.taskId, minimizeReason = MinimizeReason.TASK_LIMIT) + ) + } + + @Test + fun getMinimizingTask_activeTaskTransition_returnsTask() { + val transition = Binder() + val task = setUpFreeformTask() + addPendingMinimizeChange( + transition, + taskId = task.taskId, + minimizeReason = MinimizeReason.TASK_LIMIT, + ) + val transitionInfo = + TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build() + + desktopTasksLimiter + .getTransitionObserver() + .onTransitionReady( + transition, + transitionInfo, + /* startTransaction= */ StubTransaction(), + /* finishTransaction= */ StubTransaction(), + ) + + assertThat(desktopTasksLimiter.getMinimizingTask(transition)) + .isEqualTo( + createTaskDetails( + taskId = task.taskId, + transitionInfo = transitionInfo, + minimizeReason = MinimizeReason.TASK_LIMIT, + ) + ) + } + private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { val task = createFreeformTask(displayId) `when`(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) @@ -640,6 +660,20 @@ class DesktopTasksLimiterTest : ShellTestCase() { return task } + private fun createTaskDetails( + displayId: Int = DEFAULT_DISPLAY, + taskId: Int, + transitionInfo: TransitionInfo? = null, + minimizeReason: MinimizeReason? = null, + ) = DesktopTasksLimiter.TaskDetails(displayId, taskId, transitionInfo, minimizeReason) + + fun addPendingMinimizeChange( + transition: IBinder, + displayId: Int = DEFAULT_DISPLAY, + taskId: Int, + minimizeReason: MinimizeReason = MinimizeReason.TASK_LIMIT, + ) = desktopTasksLimiter.addPendingMinimizeChange(transition, displayId, taskId, minimizeReason) + private fun markTaskVisible(task: RunningTaskInfo) { desktopTaskRepo.updateTask(task.displayId, task.taskId, isVisible = true) } 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 79e9b9c8cd77..b4791642663a 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 @@ -61,6 +61,7 @@ import com.android.window.flags.Flags import com.android.wm.shell.R import com.android.wm.shell.desktopmode.DesktopImmersiveController import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction @@ -272,7 +273,7 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest onClickListenerCaptor.value.onClick(view) - verify(mockDesktopTasksController).minimizeTask(decor.mTaskInfo) + verify(mockDesktopTasksController).minimizeTask(decor.mTaskInfo, MinimizeReason.MINIMIZE_BUTTON) } @Test diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index a935aacacf95..848ea0f077ba 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -531,6 +531,7 @@ android_library { "-Adagger.fastInit=enabled", "-Adagger.explicitBindingConflictsWithInject=ERROR", "-Adagger.strictMultibindingValidation=enabled", + "-Adagger.useBindingGraphFix=ENABLED", "-Aroom.schemaLocation=frameworks/base/packages/SystemUI/schemas", ], kotlincflags: ["-Xjvm-default=all"], @@ -757,6 +758,10 @@ android_library { // TODO(b/352363800): Why do we need this? "-J-Xmx8192M", ], + javacflags: [ + "-Adagger.useBindingGraphFix=ENABLED", + ], + aaptflags: [ "--extra-packages", "com.android.systemui", @@ -847,7 +852,6 @@ android_robolectric_test { "androidx.test.ext.truth", ], - instrumentation_for: "SystemUIRobo-stub", java_resource_dirs: ["tests/robolectric/config"], plugins: [ @@ -884,7 +888,6 @@ android_robolectric_test { "androidx.test.ext.truth", ], - instrumentation_for: "SystemUIRobo-stub", java_resource_dirs: ["tests/robolectric/config"], plugins: [ diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 70a74f064563..3c0480d150e0 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -1065,8 +1065,7 @@ private fun EmptyStateCta(contentPadding: PaddingValues, viewModel: BaseCommunal ) { Icon( imageVector = Icons.Default.Add, - contentDescription = - stringResource(R.string.label_for_button_in_empty_state_cta), + contentDescription = null, modifier = Modifier.size(24.dp), ) Spacer(Modifier.width(ButtonDefaults.IconSpacing)) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt index 3295dde55238..bcd4d925814b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt @@ -28,6 +28,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.internal.logging.UiEventLogger import com.android.systemui.animation.Expandable @@ -54,7 +55,7 @@ constructor( VolumePanelUiEvent.VOLUME_PANEL_SPATIAL_AUDIO_POP_UP_SHOWN, 0, null, - viewModel.spatialAudioButtons.value.indexOfFirst { it.button.isActive } + viewModel.spatialAudioButtons.value.indexOfFirst { it.button.isActive }, ) val gravity = horizontalGravity or Gravity.BOTTOM volumePanelPopup.show(expandable, gravity, { Title() }, { Content(it) }) @@ -95,14 +96,14 @@ constructor( icon = { Icon(icon = buttonViewModel.button.icon) }, label = { Text( - modifier = Modifier.basicMarquee(), text = label, style = MaterialTheme.typography.labelMedium, color = LocalContentColor.current, textAlign = TextAlign.Center, - maxLines = 2 + maxLines = 1, + overflow = TextOverflow.Ellipsis, ) - } + }, ) } } diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt index 4b5e9de2cce7..72304a19c17d 100644 --- a/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt +++ b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt @@ -75,6 +75,7 @@ constructor( private val maxSize: Int, private val logcatEchoTracker: LogcatEchoTracker, private val systrace: Boolean = true, + private val systraceTrackName: String = DEFAULT_LOGBUFFER_TRACK_NAME, ) : MessageBuffer { private val buffer = RingBuffer(maxSize) { LogMessageImpl.create() } @@ -244,10 +245,11 @@ constructor( } private fun echoToSystrace(level: LogLevel, tag: String, strMessage: String) { + if (!Trace.isEnabled()) return Trace.instantForTrack( Trace.TRACE_TAG_APP, - "UI Events", - "$name - ${level.shortString} $tag: $strMessage" + systraceTrackName, + "$name - ${level.shortString} $tag: $strMessage", ) } @@ -261,6 +263,10 @@ constructor( LogLevel.WTF -> Log.wtf(message.tag, strMessage, message.exception) } } + + companion object { + const val DEFAULT_LOGBUFFER_TRACK_NAME = "UI Events" + } } private const val TAG = "LogBuffer" diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt index 4936f8559bfb..d782d1e2612c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt @@ -15,16 +15,25 @@ */ package com.android.systemui.keyguard.ui.viewmodel +import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectValues +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED +import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING +import com.android.systemui.keyguard.shared.model.TransitionState.STARTED import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.ui.transitions.blurConfig import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @@ -46,17 +55,33 @@ class AodToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() { transitionProgress = listOf(0.0f, 0.0f, 0.3f, 0.4f, 0.5f, 1.0f), startValue = kosmos.blurConfig.maxBlurRadiusPx, endValue = kosmos.blurConfig.maxBlurRadiusPx, - transitionFactory = { value, state -> - TransitionStep( - from = KeyguardState.AOD, - to = KeyguardState.PRIMARY_BOUNCER, - value = value, - transitionState = state, - ownerName = "AodToPrimaryBouncerTransitionViewModelTest", - ) - }, + transitionFactory = ::step, actualValuesProvider = { values }, checkInterpolatedValues = false, ) } + + @Test + @EnableFlags(Flags.FLAG_BOUNCER_UI_REVAMP) + fun aodToPrimaryBouncerHidesLockscreen() = + testScope.runTest { + val lockscreenAlpha by collectValues(underTest.lockscreenAlpha) + val notificationAlpha by collectValues(underTest.notificationAlpha) + + val transitionSteps = listOf(step(0.0f, STARTED), step(0.5f), step(1.0f, FINISHED)) + kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(transitionSteps, testScope) + runCurrent() + + lockscreenAlpha.forEach { assertThat(it).isEqualTo(0.0f) } + notificationAlpha.forEach { assertThat(it).isEqualTo(0.0f) } + } + + private fun step(value: Float, transitionState: TransitionState = RUNNING) = + TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.PRIMARY_BOUNCER, + value = value, + transitionState = transitionState, + ownerName = "AodToPrimaryBouncerTransitionViewModelTest", + ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt index 0d487509a83f..4d58f7ab118e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt @@ -16,21 +16,26 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING +import com.android.systemui.keyguard.shared.model.TransitionState.STARTED import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.ui.transitions.blurConfig import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -85,6 +90,21 @@ class DozingToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() { ) } + @Test + @EnableFlags(Flags.FLAG_BOUNCER_UI_REVAMP) + fun dozingToPrimaryBouncerHidesLockscreen() = + testScope.runTest { + val lockscreenAlpha by collectValues(underTest.lockscreenAlpha) + val notificationAlpha by collectValues(underTest.notificationAlpha) + + val transitionSteps = listOf(step(0.0f, STARTED), step(0.5f), step(1.0f, FINISHED)) + kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(transitionSteps, testScope) + runCurrent() + + lockscreenAlpha.forEach { assertThat(it).isEqualTo(0.0f) } + notificationAlpha.forEach { assertThat(it).isEqualTo(0.0f) } + } + private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep { return TransitionStep( from = KeyguardState.DOZING, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt index a06353171c33..6a33b5f58820 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt @@ -54,7 +54,7 @@ class QSTileLoggerTest : SysuiTestCase() { @Before fun setup() { MockitoAnnotations.initMocks(this) - whenever(logBufferFactory.create(any(), any(), any(), any())).thenReturn(logBuffer) + whenever(logBufferFactory.create(any(), any(), any(), any(), any())).thenReturn(logBuffer) val tileSpec: TileSpec = TileSpec.create("chatty_tile") underTest = QSTileLogger(mapOf(tileSpec to chattyLogBuffer), logBufferFactory, statusBarController) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt index 92271198cac0..8d90d38a9eca 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt @@ -212,7 +212,11 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() { } @Test - @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME) + @EnableFlags( + PromotedNotificationUi.FLAG_NAME, + StatusBarNotifChips.FLAG_NAME, + android.app.Flags.FLAG_API_RICH_ONGOING, + ) fun extractContent_fromProgressStyle() { val entry = createEntry { setStyle(ProgressStyle().addProgressSegment(Segment(100)).setProgress(75)) diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java index 55be9f79e598..ca98cbf20c3a 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java @@ -140,6 +140,11 @@ public interface ActivityStarter { void postStartActivityDismissingKeyguard(Intent intent, int delay, @Nullable ActivityTransitionAnimator.Controller animationController, @Nullable String customMessage); + /** Posts a start activity intent that dismisses keyguard. */ + void postStartActivityDismissingKeyguard(Intent intent, int delay, + @Nullable ActivityTransitionAnimator.Controller animationController, + @Nullable String customMessage, + @Nullable UserHandle userHandle); void postStartActivityDismissingKeyguard(PendingIntent intent); /** diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl index 2b71c87bfa27..d363e524a9f2 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl @@ -23,7 +23,7 @@ import android.os.IRemoteCallback; import android.view.MotionEvent; import com.android.systemui.shared.recents.ISystemUiProxy; -// Next ID: 36 +// Next ID: 38 oneway interface IOverviewProxy { void onActiveNavBarRegionChanges(in Region activeRegion) = 11; @@ -144,4 +144,14 @@ oneway interface IOverviewProxy { * TouchInteractionService is expected to send the reply once it has finished cleaning up. */ void onUnbind(IRemoteCallback reply) = 35; + + /** + * Sent when {@link TaskbarDelegate#onDisplayReady} is called. + */ + void onDisplayReady(int displayId) = 36; + + /** + * Sent when {@link TaskbarDelegate#onDisplayRemoved} is called. + */ + void onDisplayRemoved(int displayId) = 37; } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt index e3b55874de6f..26bf0bc258e8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.ui.viewmodel +import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor import com.android.systemui.keyguard.shared.model.Edge @@ -29,6 +30,7 @@ import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow /** * Breaks down AOD->PRIMARY BOUNCER transition into discrete steps for corresponding views to @@ -54,6 +56,12 @@ constructor(blurConfig: BlurConfig, animationFlow: KeyguardTransitionAnimationFl override val windowBlurRadius: Flow<Float> = transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx) + val lockscreenAlpha: Flow<Float> = + if (Flags.bouncerUiRevamp()) transitionAnimation.immediatelyTransitionTo(0.0f) + else emptyFlow() + + val notificationAlpha = lockscreenAlpha + override val notificationBlurRadius: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0.0f) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt index c937d5c6453d..d9ca267f9445 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.ui.viewmodel +import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor.Companion.TO_PRIMARY_BOUNCER_DURATION import com.android.systemui.keyguard.shared.model.Edge @@ -29,6 +30,7 @@ import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow /** * Breaks down DOZING->PRIMARY BOUNCER transition into discrete steps for corresponding views to @@ -64,6 +66,13 @@ constructor(private val blurConfig: BlurConfig, animationFlow: KeyguardTransitio }, onFinish = { blurConfig.maxBlurRadiusPx }, ) + + val lockscreenAlpha: Flow<Float> = + if (Flags.bouncerUiRevamp()) transitionAnimation.immediatelyTransitionTo(0.0f) + else emptyFlow() + + val notificationAlpha: Flow<Float> = lockscreenAlpha + override val notificationBlurRadius: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index eaba5d5a149c..e51e05b8ab61 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -96,9 +96,12 @@ constructor( private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel, private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel, + private val aodToPrimaryBouncerTransitionViewModel: AodToPrimaryBouncerTransitionViewModel, private val dozingToGoneTransitionViewModel: DozingToGoneTransitionViewModel, private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel, private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel, + private val dozingToPrimaryBouncerTransitionViewModel: + DozingToPrimaryBouncerTransitionViewModel, private val dreamingToAodTransitionViewModel: DreamingToAodTransitionViewModel, private val dreamingToGoneTransitionViewModel: DreamingToGoneTransitionViewModel, private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel, @@ -243,9 +246,11 @@ constructor( aodToGoneTransitionViewModel.lockscreenAlpha(viewState), aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState), aodToOccludedTransitionViewModel.lockscreenAlpha(viewState), + aodToPrimaryBouncerTransitionViewModel.lockscreenAlpha, dozingToGoneTransitionViewModel.lockscreenAlpha(viewState), dozingToLockscreenTransitionViewModel.lockscreenAlpha, dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState), + dozingToPrimaryBouncerTransitionViewModel.lockscreenAlpha, dreamingToAodTransitionViewModel.lockscreenAlpha, dreamingToGoneTransitionViewModel.lockscreenAlpha, dreamingToLockscreenTransitionViewModel.lockscreenAlpha, diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt index 6351d7d28d07..c9d6f81dc79c 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt @@ -18,6 +18,7 @@ package com.android.systemui.log import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager +import com.android.systemui.log.LogBuffer.Companion.DEFAULT_LOGBUFFER_TRACK_NAME import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize import com.android.systemui.log.echo.LogcatEchoTrackerAlways import javax.inject.Inject @@ -27,7 +28,7 @@ class LogBufferFactory @Inject constructor( private val dumpManager: DumpManager, - private val logcatEchoTracker: LogcatEchoTracker + private val logcatEchoTracker: LogcatEchoTracker, ) { @JvmOverloads fun create( @@ -35,9 +36,11 @@ constructor( maxSize: Int, systrace: Boolean = true, alwaysLogToLogcat: Boolean = false, + systraceTrackName: String = DEFAULT_LOGBUFFER_TRACK_NAME, ): LogBuffer { val echoTracker = if (alwaysLogToLogcat) LogcatEchoTrackerAlways else logcatEchoTracker - val buffer = LogBuffer(name, adjustMaxSize(maxSize), echoTracker, systrace) + val buffer = + LogBuffer(name, adjustMaxSize(maxSize), echoTracker, systrace, systraceTrackName) dumpManager.registerBuffer(name, buffer) return buffer } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index 3f14b55e46a1..9270fff61c43 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -236,11 +236,29 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, @Override public void onDisplayReady(int displayId) { CommandQueue.Callbacks.super.onDisplayReady(displayId); + if (mOverviewProxyService.getProxy() == null) { + return; + } + + try { + mOverviewProxyService.getProxy().onDisplayReady(displayId); + } catch (RemoteException e) { + Log.e(TAG, "onDisplayReady() failed", e); + } } @Override public void onDisplayRemoved(int displayId) { CommandQueue.Callbacks.super.onDisplayRemoved(displayId); + if (mOverviewProxyService.getProxy() == null) { + return; + } + + try { + mOverviewProxyService.getProxy().onDisplayRemoved(displayId); + } catch (RemoteException e) { + Log.e(TAG, "onDisplayRemoved() failed", e); + } } // Separated into a method to keep setDependencies() clean/readable. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/dagger/NotificationsLogModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/dagger/NotificationsLogModule.kt index d3359d39e959..6bcce3e21998 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/dagger/NotificationsLogModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/dagger/NotificationsLogModule.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.logging.dagger +import com.android.app.tracing.TrackGroupUtils.trackGroup import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBufferFactory @@ -44,7 +45,11 @@ object NotificationsLogModule { @SysUISingleton @NotificationHeadsUpLog fun provideNotificationHeadsUpLogBuffer(factory: LogBufferFactory): LogBuffer { - return factory.create("NotifHeadsUpLog", 1000) + return factory.create( + "NotifHeadsUpLog", + 1000, + systraceTrackName = notifPipelineTrack("NotifHeadsUpLog"), + ) } /** Provides a logging buffer for logs related to inflation of notifications. */ @@ -52,7 +57,11 @@ object NotificationsLogModule { @SysUISingleton @NotifInflationLog fun provideNotifInflationLogBuffer(factory: LogBufferFactory): LogBuffer { - return factory.create("NotifInflationLog", 250) + return factory.create( + "NotifInflationLog", + 250, + systraceTrackName = notifPipelineTrack("NotifInflationLog"), + ) } /** Provides a logging buffer for all logs related to the data layer of notifications. */ @@ -60,7 +69,11 @@ object NotificationsLogModule { @SysUISingleton @NotifInteractionLog fun provideNotifInteractionLogBuffer(factory: LogBufferFactory): LogBuffer { - return factory.create("NotifInteractionLog", 50) + return factory.create( + "NotifInteractionLog", + 50, + systraceTrackName = notifPipelineTrack("NotifInteractionLog"), + ) } /** Provides a logging buffer for notification interruption calculations. */ @@ -68,7 +81,11 @@ object NotificationsLogModule { @SysUISingleton @NotificationInterruptLog fun provideNotificationInterruptLogBuffer(factory: LogBufferFactory): LogBuffer { - return factory.create("NotifInterruptLog", 100) + return factory.create( + "NotifInterruptLog", + 100, + systraceTrackName = notifPipelineTrack("NotifInterruptLog"), + ) } /** Provides a logging buffer for all logs related to notifications on the lockscreen. */ @@ -91,7 +108,12 @@ object NotificationsLogModule { if (Compile.IS_DEBUG && notifPipelineFlags.isDevLoggingEnabled()) { maxSize *= 10 } - return factory.create("NotifLog", maxSize, Compile.IS_DEBUG /* systrace */) + return factory.create( + "NotifLog", + maxSize, + /* systrace= */ Compile.IS_DEBUG, + systraceTrackName = notifPipelineTrack("NotifLog"), + ) } /** Provides a logging buffer for all logs related to remote input controller. */ @@ -107,7 +129,11 @@ object NotificationsLogModule { @SysUISingleton @NotificationRenderLog fun provideNotificationRenderLogBuffer(factory: LogBufferFactory): LogBuffer { - return factory.create("NotifRenderLog", 100) + return factory.create( + "NotifRenderLog", + 100, + systraceTrackName = notifPipelineTrack("NotifRenderLog"), + ) } /** Provides a logging buffer for all logs related to managing notification sections. */ @@ -150,3 +176,13 @@ object NotificationsLogModule { return factory.create("VisualStabilityLog", 50, /* maxSize */ false /* systrace */) } } + +private const val NOTIF_PIPELINE_TRACK_GROUP_NAME = "Notification pipeline" + +/** + * This generates a track name that is hierarcically collapsed inside + * [NOTIF_PIPELINE_TRACK_GROUP_NAME] in perfetto traces. + */ +private fun notifPipelineTrack(trackName: String): String { + return trackGroup(NOTIF_PIPELINE_TRACK_GROUP_NAME, trackName) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 495b50869458..f57107141f61 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -127,7 +127,6 @@ import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrim import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrollState; import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; -import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.policy.ScrollAdapter; import com.android.systemui.statusbar.policy.SplitShadeStateController; import com.android.systemui.util.Assert; @@ -282,11 +281,9 @@ public class NotificationStackScrollLayout private boolean mExpandedInThisMotion; private boolean mShouldShowShelfOnly; protected boolean mScrollingEnabled; - private boolean mIsCurrentUserSetup; protected FooterView mFooterView; protected EmptyShadeView mEmptyShadeView; private boolean mClearAllInProgress; - private FooterClearAllListener mFooterClearAllListener; private boolean mFlingAfterUpEvent; /** * Was the scroller scrolled to the top when the down motion was observed? @@ -467,7 +464,6 @@ public class NotificationStackScrollLayout boolean mHeadsUpAnimatingAway; private Consumer<Boolean> mHeadsUpAnimatingAwayListener; private int mStatusBarState; - private int mUpcomingStatusBarState; private boolean mHeadsUpGoingAwayAnimationsAllowed = true; private final Runnable mReflingAndAnimateScroll = this::animateScroll; private int mCornerRadius; @@ -498,7 +494,6 @@ public class NotificationStackScrollLayout private float mLastSentExpandedHeight; private boolean mWillExpand; private int mGapHeight; - private boolean mIsRemoteInputActive; /** * The extra inset during the full shade transition @@ -572,10 +567,8 @@ public class NotificationStackScrollLayout private boolean mDismissUsingRowTranslationX = true; private ExpandableNotificationRow mTopHeadsUpRow; private NotificationStackScrollLayoutController.TouchHandler mTouchHandler; - private final ScreenOffAnimationController mScreenOffAnimationController; private boolean mShouldUseSplitNotificationShade; private boolean mShouldSkipTopPaddingAnimationAfterFold = false; - private boolean mHasFilteredOutSeenNotifications; @Nullable private SplitShadeStateController mSplitShadeStateController = null; private boolean mIsSmallLandscapeLockscreenEnabled = false; private boolean mSuppressHeightUpdates; @@ -636,9 +629,6 @@ public class NotificationStackScrollLayout }; @Nullable - private OnClickListener mManageButtonClickListener; - - @Nullable private WallpaperInteractor mWallpaperInteractor; public NotificationStackScrollLayout(Context context, AttributeSet attrs) { @@ -650,8 +640,6 @@ public class NotificationStackScrollLayout mDebugLines = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_LINES); mDebugRemoveAnimation = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION); mSectionsManager = Dependency.get(NotificationSectionsManager.class); - mScreenOffAnimationController = - Dependency.get(ScreenOffAnimationController.class); mSectionsManager.initialize(this); mSections = mSectionsManager.createSectionsForBuckets(); @@ -5403,7 +5391,6 @@ public class NotificationStackScrollLayout println(pw, "suppressChildrenMeasureLayout", mSuppressChildrenMeasureAndLayout); println(pw, "scrollY", mAmbientState.getScrollY()); println(pw, "showShelfOnly", mShouldShowShelfOnly); - println(pw, "isCurrentUserSetup", mIsCurrentUserSetup); println(pw, "hideAmount", mAmbientState.getHideAmount()); println(pw, "ambientStateSwipingUp", mAmbientState.isSwipingUp()); println(pw, "maxDisplayedNotifications", mMaxDisplayedNotifications); @@ -6793,10 +6780,6 @@ public class NotificationStackScrollLayout void onClearAll(@SelectedRows int selectedRows); } - interface FooterClearAllListener { - void onClearAll(); - } - interface ClearAllAnimationListener { void onAnimationEnd( List<ExpandableNotificationRow> viewsToRemove, @SelectedRows int selectedRows); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index f0455fc3a22b..c1d022600559 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -49,9 +49,11 @@ import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.AodToOccludedTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.AodToPrimaryBouncerTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.DozingToGlanceableHubTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.DozingToOccludedTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.DozingToPrimaryBouncerTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.GoneToAodTransitionViewModel @@ -131,9 +133,12 @@ constructor( private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel, private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel, + private val aodToPrimaryBouncerTransitionViewModel: AodToPrimaryBouncerTransitionViewModel, dozingToGlanceableHubTransitionViewModel: DozingToGlanceableHubTransitionViewModel, private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel, private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel, + private val dozingToPrimaryBouncerTransitionViewModel: + DozingToPrimaryBouncerTransitionViewModel, private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel, private val glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel, @@ -554,8 +559,10 @@ constructor( aodToGoneTransitionViewModel.notificationAlpha(viewState), aodToLockscreenTransitionViewModel.notificationAlpha, aodToOccludedTransitionViewModel.lockscreenAlpha(viewState), + aodToPrimaryBouncerTransitionViewModel.notificationAlpha, dozingToLockscreenTransitionViewModel.lockscreenAlpha, dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState), + dozingToPrimaryBouncerTransitionViewModel.notificationAlpha, dreamingToLockscreenTransitionViewModel.lockscreenAlpha, goneToAodTransitionViewModel.notificationAlpha, goneToDreamingTransitionViewModel.lockscreenAlpha, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt index 4751293a16cc..5a63c0cd84e6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt @@ -312,6 +312,25 @@ constructor( } } + override fun postStartActivityDismissingKeyguard( + intent: Intent, + delay: Int, + animationController: ActivityTransitionAnimator.Controller?, + customMessage: String?, + userHandle: UserHandle?, + ) { + postOnUiThread(delay) { + activityStarterInternal.startActivityDismissingKeyguard( + intent = intent, + onlyProvisioned = true, + dismissShade = true, + animationController = animationController, + customMessage = customMessage, + userHandle = userHandle, + ) + } + } + override fun dismissKeyguardThenExecute( action: OnDismissAction, cancel: Runnable?, diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java index 411e06ed1339..0c8dc11f4327 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java @@ -293,7 +293,7 @@ public class QuickAccessWalletController { intent = getSysUiWalletIntent(); } startQuickAccessViaIntent(intent, hasCard, activityStarter, - animationController); + animationController, mQuickAccessWalletClient.getUser()); }); } @@ -323,7 +323,8 @@ public class QuickAccessWalletController { private void startQuickAccessViaIntent(Intent intent, boolean hasCard, ActivityStarter activityStarter, - ActivityTransitionAnimator.Controller animationController) { + ActivityTransitionAnimator.Controller animationController, + UserHandle user) { if (hasCard) { activityStarter.startActivity(intent, true /* dismissShade */, animationController, true /* showOverLockscreenWhenLocked */); @@ -331,7 +332,9 @@ public class QuickAccessWalletController { activityStarter.postStartActivityDismissingKeyguard( intent, /* delay= */ 0, - animationController); + animationController, + null, + user); } } diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java index 111492c3e227..18e1b6eb323d 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java @@ -163,15 +163,13 @@ public class WalletActivity extends ComponentActivity implements if (mKeyguardStateController.isUnlocked()) { mUiEventLogger.log(WalletUiEvent.QAW_SHOW_ALL); - mActivityStarter.startActivity( - mWalletClient.createWalletIntent(), true); + startWalletActivity(); finish(); } else { mUiEventLogger.log(WalletUiEvent.QAW_UNLOCK_FROM_SHOW_ALL_BUTTON); mKeyguardDismissUtil.executeWhenUnlocked(() -> { mUiEventLogger.log(WalletUiEvent.QAW_SHOW_ALL); - mActivityStarter.startActivity( - mWalletClient.createWalletIntent(), true); + startWalletActivity(); finish(); return false; }, false, true); @@ -193,6 +191,11 @@ public class WalletActivity extends ComponentActivity implements }); } + private void startWalletActivity() { + mActivityStarter.startActivity(mWalletClient.createWalletIntent(), true, + null, true, mWalletClient.getUser()); + } + @Override protected void onStart() { super.onStart(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java index 733e2edaec84..8e97c86ba507 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java @@ -17,6 +17,7 @@ package com.android.systemui.wallet.controller; import static android.service.quickaccesswallet.Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotSame; @@ -35,6 +36,7 @@ import static org.mockito.Mockito.when; import android.app.PendingIntent; import android.app.role.RoleManager; import android.content.Intent; +import android.os.UserHandle; import android.platform.test.annotations.EnableFlags; import android.service.quickaccesswallet.GetWalletCardsRequest; import android.service.quickaccesswallet.QuickAccessWalletClient; @@ -59,7 +61,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.mockito.Spy; import java.util.List; @@ -98,6 +99,7 @@ public class QuickAccessWalletControllerTest extends SysuiTestCase { when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(true); when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(true); when(mQuickAccessWalletClient.isWalletFeatureAvailableWhenDeviceLocked()).thenReturn(true); + when(mQuickAccessWalletClient.getUser()).thenReturn(UserHandle.of(0)); mClock.setElapsedRealtime(100L); doAnswer(invocation -> { @@ -269,7 +271,8 @@ public class QuickAccessWalletControllerTest extends SysuiTestCase { public void getQuickAccessUiIntent_noCards_noPendingIntent_startsWalletActivity() { mController.startQuickAccessUiIntent(mActivityStarter, mAnimationController, false); verify(mActivityStarter).postStartActivityDismissingKeyguard(mIntentCaptor.capture(), eq(0), - any(ActivityTransitionAnimator.Controller.class)); + any(ActivityTransitionAnimator.Controller.class), eq(null), + eq(UserHandle.of(0))); Intent intent = mIntentCaptor.getValue(); assertEquals(intent.getAction(), Intent.ACTION_VIEW); assertEquals( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt index abbfa93edd17..1c0f97d294df 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt @@ -56,9 +56,11 @@ val Kosmos.keyguardRootViewModel by Fixture { aodToGoneTransitionViewModel = aodToGoneTransitionViewModel, aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel, aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel, + aodToPrimaryBouncerTransitionViewModel = aodToPrimaryBouncerTransitionViewModel, dozingToGoneTransitionViewModel = dozingToGoneTransitionViewModel, dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel, dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel, + dozingToPrimaryBouncerTransitionViewModel = dozingToPrimaryBouncerTransitionViewModel, dreamingToAodTransitionViewModel = dreamingToAodTransitionViewModel, dreamingToGoneTransitionViewModel = dreamingToGoneTransitionViewModel, dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt index 60e092c9709b..8461da77796d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt @@ -28,9 +28,11 @@ import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel import com.android.systemui.keyguard.ui.viewmodel.aodToGoneTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.aodToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.aodToOccludedTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.aodToPrimaryBouncerTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.dozingToGlanceableHubTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.dozingToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.dozingToOccludedTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.dozingToPrimaryBouncerTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.dreamingToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.goneToAodTransitionViewModel @@ -78,9 +80,11 @@ val Kosmos.sharedNotificationContainerViewModel by Fixture { aodToGoneTransitionViewModel = aodToGoneTransitionViewModel, aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel, aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel, + aodToPrimaryBouncerTransitionViewModel = aodToPrimaryBouncerTransitionViewModel, dozingToGlanceableHubTransitionViewModel = dozingToGlanceableHubTransitionViewModel, dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel, dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel, + dozingToPrimaryBouncerTransitionViewModel = dozingToPrimaryBouncerTransitionViewModel, dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel, goneToAodTransitionViewModel = goneToAodTransitionViewModel, goneToDozingTransitionViewModel = goneToDozingTransitionViewModel, diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java index b94fa2f59162..8b758d29a2ac 100644 --- a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java +++ b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java @@ -39,6 +39,8 @@ import android.view.MotionEvent.PointerProperties; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; +import androidx.annotation.VisibleForTesting; + /** * Implements "Automatically click on mouse stop" feature. * @@ -69,10 +71,10 @@ public class AutoclickController extends BaseEventStreamTransformation { private final int mUserId; // Lazily created on the first mouse motion event. - private ClickScheduler mClickScheduler; - private AutoclickSettingsObserver mAutoclickSettingsObserver; - private AutoclickIndicatorScheduler mAutoclickIndicatorScheduler; - private AutoclickIndicatorView mAutoclickIndicatorView; + @VisibleForTesting ClickScheduler mClickScheduler; + @VisibleForTesting AutoclickSettingsObserver mAutoclickSettingsObserver; + @VisibleForTesting AutoclickIndicatorScheduler mAutoclickIndicatorScheduler; + @VisibleForTesting AutoclickIndicatorView mAutoclickIndicatorView; private WindowManager mWindowManager; public AutoclickController(Context context, int userId, AccessibilityTraceManager trace) { @@ -360,7 +362,8 @@ public class AutoclickController extends BaseEventStreamTransformation { * moving. The click is first scheduled when a mouse movement is detected, and then further * delayed on every sufficient mouse movement. */ - final private class ClickScheduler implements Runnable { + @VisibleForTesting + final class ClickScheduler implements Runnable { /** * Minimal distance pointer has to move relative to anchor in order for movement not to be * discarded as noise. Anchor is the position of the last MOVE event that was not considered @@ -474,6 +477,11 @@ public class AutoclickController extends BaseEventStreamTransformation { } } + @VisibleForTesting + int getDelayForTesting() { + return mDelay; + } + /** * Updates the time at which click sequence should occur. * diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java b/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java index bf5015176f8c..f87dcdb200bb 100644 --- a/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java +++ b/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java @@ -28,6 +28,8 @@ import android.view.View; import android.view.accessibility.AccessibilityManager; import android.view.animation.LinearInterpolator; +import androidx.annotation.VisibleForTesting; + // A visual indicator for the autoclick feature. public class AutoclickIndicatorView extends View { private static final String TAG = AutoclickIndicatorView.class.getSimpleName(); @@ -37,7 +39,7 @@ public class AutoclickIndicatorView extends View { static final int MINIMAL_ANIMATION_DURATION = 50; - private float mRadius = AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT; + private int mRadius = AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT; private final Paint mPaint; @@ -112,6 +114,11 @@ public class AutoclickIndicatorView extends View { mRadius = radius; } + @VisibleForTesting + int getRadiusForTesting() { + return mRadius; + } + public void redrawIndicator() { showIndicator = true; invalidate(); diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java index abfb8268bd9a..df47c98d6433 100644 --- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java +++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java @@ -98,6 +98,8 @@ public class ContextualSearchManagerService extends SystemService { private static final int MSG_INVALIDATE_TOKEN = 1; private static final int MAX_TOKEN_VALID_DURATION_MS = 1_000 * 60 * 10; // 10 minutes + private static final boolean DEBUG = false; + private final Context mContext; private final ActivityTaskManagerInternal mAtmInternal; private final PackageManagerInternal mPackageManager; @@ -121,6 +123,7 @@ public class ContextualSearchManagerService extends SystemService { final Bundle data, final int activityIndex, final int activityCount) { + final IContextualSearchCallback callback; synchronized (mLock) { callback = mStateCallback; @@ -160,7 +163,7 @@ public class ContextualSearchManagerService extends SystemService { public ContextualSearchManagerService(@NonNull Context context) { super(context); - if (DEBUG_USER) Log.d(TAG, "ContextualSearchManagerService created"); + if (DEBUG) Log.d(TAG, "ContextualSearchManagerService created"); mContext = context; mAtmInternal = Objects.requireNonNull( LocalServices.getService(ActivityTaskManagerInternal.class)); @@ -206,7 +209,7 @@ public class ContextualSearchManagerService extends SystemService { mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_PACKAGE); mTemporaryHandler = null; } - if (DEBUG_USER) Log.d(TAG, "mTemporaryPackage reset."); + if (DEBUG) Log.d(TAG, "mTemporaryPackage reset."); mTemporaryPackage = null; updateSecureSetting(); } @@ -239,7 +242,7 @@ public class ContextualSearchManagerService extends SystemService { mTemporaryPackage = temporaryPackage; updateSecureSetting(); mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_PACKAGE, durationMs); - if (DEBUG_USER) Log.d(TAG, "mTemporaryPackage set to " + mTemporaryPackage); + if (DEBUG) Log.d(TAG, "mTemporaryPackage set to " + mTemporaryPackage); } } @@ -256,7 +259,7 @@ public class ContextualSearchManagerService extends SystemService { + durationMs + ")"); } mTokenValidDurationMs = durationMs; - if (DEBUG_USER) Log.d(TAG, "mTokenValidDurationMs set to " + durationMs); + if (DEBUG) Log.d(TAG, "mTokenValidDurationMs set to " + durationMs); } } @@ -268,12 +271,12 @@ public class ContextualSearchManagerService extends SystemService { private Intent getResolvedLaunchIntent(int userId) { synchronized (this) { - if(DEBUG_USER) Log.d(TAG, "Attempting to getResolvedLaunchIntent"); + if(DEBUG) Log.d(TAG, "Attempting to getResolvedLaunchIntent"); // If mTemporaryPackage is not null, use it to get the ContextualSearch intent. String csPkgName = getContextualSearchPackageName(); if (csPkgName.isEmpty()) { // Return null if csPackageName is not specified. - if (DEBUG_USER) Log.w(TAG, "getContextualSearchPackageName is empty"); + if (DEBUG) Log.w(TAG, "getContextualSearchPackageName is empty"); return null; } Intent launchIntent = new Intent( @@ -282,12 +285,12 @@ public class ContextualSearchManagerService extends SystemService { ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivityAsUser( launchIntent, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId); if (resolveInfo == null) { - if (DEBUG_USER) Log.w(TAG, "resolveInfo is null"); + if (DEBUG) Log.w(TAG, "resolveInfo is null"); return null; } ComponentName componentName = resolveInfo.getComponentInfo().getComponentName(); if (componentName == null) { - if (DEBUG_USER) Log.w(TAG, "componentName is null"); + if (DEBUG) Log.w(TAG, "componentName is null"); return null; } launchIntent.setComponent(componentName); @@ -298,11 +301,11 @@ public class ContextualSearchManagerService extends SystemService { private Intent getContextualSearchIntent(int entrypoint, int userId, CallbackToken mToken) { final Intent launchIntent = getResolvedLaunchIntent(userId); if (launchIntent == null) { - if (DEBUG_USER) Log.w(TAG, "Failed getContextualSearchIntent: launchIntent is null"); + if (DEBUG) Log.w(TAG, "Failed getContextualSearchIntent: launchIntent is null"); return null; } - if (DEBUG_USER) Log.d(TAG, "Launch component: " + launchIntent.getComponent()); + if (DEBUG) Log.d(TAG, "Launch component: " + launchIntent.getComponent()); launchIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NO_ANIMATION | FLAG_ACTIVITY_NO_USER_ACTION | FLAG_ACTIVITY_CLEAR_TASK); launchIntent.putExtra( @@ -355,7 +358,7 @@ public class ContextualSearchManagerService extends SystemService { TYPE_NAVIGATION_BAR_PANEL, TYPE_POINTER)); } else { - if (DEBUG_USER) Log.w(TAG, "Can't capture contextual screenshot: mWmInternal is null"); + if (DEBUG) Log.w(TAG, "Can't capture contextual screenshot: mWmInternal is null"); shb = null; } final Bitmap bm = shb != null ? shb.asBitmap() : null; @@ -429,7 +432,7 @@ public class ContextualSearchManagerService extends SystemService { mTokenHandler.removeMessages(MSG_INVALIDATE_TOKEN); mTokenHandler = null; } - if (DEBUG_USER) Log.d(TAG, "mToken invalidated."); + if (DEBUG) Log.d(TAG, "mToken invalidated."); mToken = null; } } @@ -459,7 +462,7 @@ public class ContextualSearchManagerService extends SystemService { @Override public void startContextualSearch(int entrypoint) { synchronized (this) { - if (DEBUG_USER) Log.d(TAG, "startContextualSearch entrypoint: " + entrypoint); + if (DEBUG) Log.d(TAG, "startContextualSearch entrypoint: " + entrypoint); enforcePermission("startContextualSearch"); final int callingUserId = Binder.getCallingUserHandle().getIdentifier(); @@ -474,7 +477,7 @@ public class ContextualSearchManagerService extends SystemService { getContextualSearchIntent(entrypoint, callingUserId, mToken); if (launchIntent != null) { int result = invokeContextualSearchIntent(launchIntent, callingUserId); - if (DEBUG_USER) Log.d(TAG, "Launch result: " + result); + if (DEBUG) Log.d(TAG, "Launch result: " + result); } }); } @@ -484,11 +487,11 @@ public class ContextualSearchManagerService extends SystemService { public void getContextualSearchState( @NonNull IBinder token, @NonNull IContextualSearchCallback callback) { - if (DEBUG_USER) { + if (DEBUG) { Log.i(TAG, "getContextualSearchState token: " + token + ", callback: " + callback); } if (mToken == null || !mToken.getToken().equals(token)) { - if (DEBUG_USER) { + if (DEBUG) { Log.e(TAG, "getContextualSearchState: invalid token, returning error"); } try { diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java index c19d2c9091c3..21c9b876ab46 100644 --- a/services/core/java/com/android/server/display/BrightnessTracker.java +++ b/services/core/java/com/android/server/display/BrightnessTracker.java @@ -16,6 +16,9 @@ package com.android.server.display; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; + import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; @@ -1181,6 +1184,14 @@ public class BrightnessTracker { } public RootTaskInfo getFocusedStack() throws RemoteException { + if (UserManager.isVisibleBackgroundUsersEnabled()) { + // In MUMD (Multiple Users on Multiple Displays) system, the top most focused stack + // could be on the secondary display with a user signed on its display so get the + // root task info only on the default display. + return ActivityTaskManager.getService().getRootTaskInfoOnDisplay( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_UNDEFINED, + Display.DEFAULT_DISPLAY); + } return ActivityTaskManager.getService().getFocusedRootTaskInfo(); } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 0226650ec560..42a47d4a037e 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -8440,8 +8440,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mTmpConfig.updateFrom(resolvedConfig); newParentConfiguration = mTmpConfig; } - - mAppCompatController.getAspectRatioPolicy().reset(); + final AppCompatAspectRatioPolicy aspectRatioPolicy = + mAppCompatController.getAspectRatioPolicy(); + aspectRatioPolicy.reset(); mIsEligibleForFixedOrientationLetterbox = false; mResolveConfigHint.resolveTmpOverrides(mDisplayContent, newParentConfiguration, isFixedRotationTransforming()); @@ -8472,12 +8473,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds // are already calculated in resolveFixedOrientationConfiguration. // Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer. - if (!mAppCompatController.getAspectRatioPolicy() - .isLetterboxedForFixedOrientationAndAspectRatio() - && !mAppCompatController.getAspectRatioOverrides() - .hasFullscreenOverride()) { - resolveAspectRatioRestriction(newParentConfiguration); - } + aspectRatioPolicy.resolveAspectRatioRestrictionIfNeeded(newParentConfiguration); final AppCompatDisplayInsets appCompatDisplayInsets = getAppCompatDisplayInsets(); final AppCompatSizeCompatModePolicy scmPolicy = mAppCompatController.getSizeCompatModePolicy(); @@ -8509,8 +8505,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Fixed orientation letterboxing is possible on both large screen devices // with ignoreOrientationRequest enabled and on phones in split screen even with // ignoreOrientationRequest disabled. - && (mAppCompatController.getAspectRatioPolicy() - .isLetterboxedForFixedOrientationAndAspectRatio() + && (aspectRatioPolicy.isLetterboxedForFixedOrientationAndAspectRatio() // Limiting check for aspect ratio letterboxing to devices with enabled // ignoreOrientationRequest. This avoids affecting phones where apps may // not expect the change of smallestScreenWidthDp after rotation which is @@ -8518,7 +8513,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // accurate on phones shouldn't make the big difference and is expected // to be already well-tested by apps. || (isIgnoreOrientationRequest - && mAppCompatController.getAspectRatioPolicy().isAspectRatioApplied()))) { + && aspectRatioPolicy.isAspectRatioApplied()))) { // TODO(b/264034555): Use mDisplayContent to calculate smallestScreenWidthDp from all // rotations and only re-calculate if parent bounds have non-orientation size change. resolvedConfig.smallestScreenWidthDp = @@ -9007,37 +9002,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A new Rect(resolvedBounds)); } - /** - * Resolves aspect ratio restrictions for an activity. If the bounds are restricted by - * aspect ratio, the position will be adjusted later in {@link #updateResolvedBoundsPosition - * within parent's app bounds to balance the visual appearance. The policy of aspect ratio has - * higher priority than the requested override bounds. - */ - private void resolveAspectRatioRestriction(Configuration newParentConfiguration) { - final Configuration resolvedConfig = getResolvedOverrideConfiguration(); - final Rect parentAppBounds = mResolveConfigHint.mParentAppBoundsOverride; - final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds(); - final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds(); - // Use tmp bounds to calculate aspect ratio so we can know whether the activity should use - // restricted size (resolved bounds may be the requested override bounds). - mTmpBounds.setEmpty(); - final AppCompatAspectRatioPolicy aspectRatioPolicy = mAppCompatController - .getAspectRatioPolicy(); - aspectRatioPolicy.applyAspectRatioForLetterbox(mTmpBounds, parentAppBounds, parentBounds); - // If the out bounds is not empty, it means the activity cannot fill parent's app bounds, - // then they should be aligned later in #updateResolvedBoundsPosition() - if (!mTmpBounds.isEmpty()) { - resolvedBounds.set(mTmpBounds); - } - if (!resolvedBounds.isEmpty() && !resolvedBounds.equals(parentBounds)) { - // Compute the configuration based on the resolved bounds. If aspect ratio doesn't - // restrict, the bounds should be the requested override bounds. - mResolveConfigHint.mTmpOverrideDisplayInfo = getFixedRotationTransformDisplayInfo(); - computeConfigByResolveHint(resolvedConfig, newParentConfiguration); - aspectRatioPolicy.setLetterboxBoundsForAspectRatio(new Rect(resolvedBounds)); - } - } - @Override public Rect getBounds() { // TODO(b/268458693): Refactor configuration inheritance in case of translucent activities diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java index 4ecd0bec9880..ab1778a1a32e 100644 --- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java @@ -56,6 +56,8 @@ class AppCompatAspectRatioPolicy { @NonNull private final AppCompatAspectRatioState mAppCompatAspectRatioState; + private final Rect mTmpBounds = new Rect(); + AppCompatAspectRatioPolicy(@NonNull ActivityRecord activityRecord, @NonNull TransparentPolicy transparentPolicy, @NonNull AppCompatOverrides appCompatOverrides) { @@ -222,6 +224,45 @@ class AppCompatAspectRatioPolicy { return getMaxAspectRatio() != 0 || getMinAspectRatio() != 0; } + /** + * Resolves aspect ratio restrictions for an activity. If the bounds are restricted by + * aspect ratio, the position will be adjusted later in {@link #updateResolvedBoundsPosition} + * within parent's app bounds to balance the visual appearance. The policy of aspect ratio has + * higher priority than the requested override bounds. + */ + void resolveAspectRatioRestrictionIfNeeded(@NonNull Configuration newParentConfiguration) { + // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds + // are already calculated in resolveFixedOrientationConfiguration. + // Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer. + if (isLetterboxedForFixedOrientationAndAspectRatio() + || getOverrides().hasFullscreenOverride()) { + return; + } + final Configuration resolvedConfig = mActivityRecord.getResolvedOverrideConfiguration(); + final Rect parentAppBounds = + mActivityRecord.mResolveConfigHint.mParentAppBoundsOverride; + final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds(); + final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds(); + // Use tmp bounds to calculate aspect ratio so we can know whether the activity should + // use restricted size (resolved bounds may be the requested override bounds). + mTmpBounds.setEmpty(); + applyAspectRatioForLetterbox(mTmpBounds, parentAppBounds, parentBounds); + // If the out bounds is not empty, it means the activity cannot fill parent's app + // bounds, then they should be aligned later in #updateResolvedBoundsPosition(). + if (!mTmpBounds.isEmpty()) { + resolvedBounds.set(mTmpBounds); + } + if (!resolvedBounds.isEmpty() && !resolvedBounds.equals(parentBounds)) { + // Compute the configuration based on the resolved bounds. If aspect ratio doesn't + // restrict, the bounds should be the requested override bounds. + // TODO(b/384473893): Improve ActivityRecord usage here. + mActivityRecord.mResolveConfigHint.mTmpOverrideDisplayInfo = + mActivityRecord.getFixedRotationTransformDisplayInfo(); + mActivityRecord.computeConfigByResolveHint(resolvedConfig, newParentConfiguration); + setLetterboxBoundsForAspectRatio(new Rect(resolvedBounds)); + } + } + private boolean isParentFullscreenPortrait() { final WindowContainer<?> parent = mActivityRecord.getParent(); return parent != null @@ -364,6 +405,11 @@ class AppCompatAspectRatioPolicy { && !dc.getIgnoreOrientationRequest(); } + @NonNull + private AppCompatAspectRatioOverrides getOverrides() { + return mActivityRecord.mAppCompatController.getAspectRatioOverrides(); + } + private static class AppCompatAspectRatioState { // Whether the aspect ratio restrictions applied to the activity bounds // in applyAspectRatio(). diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 7a88338d8ac5..c6136f316c3e 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -5101,6 +5101,7 @@ class Task extends TaskFragment { mTranslucentActivityWaiting = r; mPendingConvertFromTranslucentActivity = r; mUndrawnActivitiesBelowTopTranslucent.clear(); + updateTaskDescription(); mHandler.sendEmptyMessageDelayed(TRANSLUCENT_TIMEOUT_MSG, TRANSLUCENT_CONVERSION_TIMEOUT); } @@ -5110,6 +5111,7 @@ class Task extends TaskFragment { + " but is " + r); } mPendingConvertFromTranslucentActivity = null; + updateTaskDescription(); } /** diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AutoclickControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AutoclickControllerTest.java new file mode 100644 index 000000000000..acce813ff659 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/accessibility/AutoclickControllerTest.java @@ -0,0 +1,242 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.accessibility; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static com.android.server.testutils.MockitoUtilsKt.eq; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertNotNull; +import static org.testng.AssertJUnit.assertNull; + +import android.content.Context; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; +import android.provider.Settings; +import android.testing.AndroidTestingRunner; +import android.testing.TestableContext; +import android.testing.TestableLooper; +import android.view.InputDevice; +import android.view.MotionEvent; +import android.view.WindowManager; +import android.view.accessibility.AccessibilityManager; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +/** Test cases for {@link AutoclickController}. */ +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class AutoclickControllerTest { + + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + + @Rule + public TestableContext mTestableContext = + new TestableContext(getInstrumentation().getContext()); + + private TestableLooper mTestableLooper; + @Mock private AccessibilityTraceManager mMockTrace; + @Mock private WindowManager mMockWindowManager; + private AutoclickController mController; + + @Before + public void setUp() { + mTestableLooper = TestableLooper.get(this); + mTestableContext.addMockSystemService(Context.WINDOW_SERVICE, mMockWindowManager); + mController = + new AutoclickController(mTestableContext, mTestableContext.getUserId(), mMockTrace); + } + + @After + public void tearDown() { + mTestableLooper.processAllMessages(); + } + + @Test + public void onMotionEvent_lazyInitClickScheduler() { + assertNull(mController.mClickScheduler); + + injectFakeMouseActionDownEvent(); + + assertNotNull(mController.mClickScheduler); + } + + @Test + public void onMotionEvent_nonMouseSource_notInitClickScheduler() { + assertNull(mController.mClickScheduler); + + injectFakeNonMouseActionDownEvent(); + + assertNull(mController.mClickScheduler); + } + + @Test + public void onMotionEvent_lazyInitAutoclickSettingsObserver() { + assertNull(mController.mAutoclickSettingsObserver); + + injectFakeMouseActionDownEvent(); + + assertNotNull(mController.mAutoclickSettingsObserver); + } + + @Test + @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void onMotionEvent_flagOn_lazyInitAutoclickIndicatorScheduler() { + assertNull(mController.mAutoclickIndicatorScheduler); + + injectFakeMouseActionDownEvent(); + + assertNotNull(mController.mAutoclickIndicatorScheduler); + } + + @Test + @DisableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void onMotionEvent_flagOff_notInitAutoclickIndicatorScheduler() { + assertNull(mController.mAutoclickIndicatorScheduler); + + injectFakeMouseActionDownEvent(); + + assertNull(mController.mAutoclickIndicatorScheduler); + } + + @Test + @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void onMotionEvent_flagOn_lazyInitAutoclickIndicatorView() { + assertNull(mController.mAutoclickIndicatorView); + + injectFakeMouseActionDownEvent(); + + assertNotNull(mController.mAutoclickIndicatorView); + } + + @Test + @DisableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void onMotionEvent_flagOff_notInitAutoclickIndicatorView() { + assertNull(mController.mAutoclickIndicatorView); + + injectFakeMouseActionDownEvent(); + + assertNull(mController.mAutoclickIndicatorView); + } + + @Test + @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void onMotionEvent_flagOn_addAutoclickIndicatorViewToWindowManager() { + injectFakeMouseActionDownEvent(); + + verify(mMockWindowManager).addView(eq(mController.mAutoclickIndicatorView), any()); + } + + @Test + @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void onDestroy_flagOn_removeAutoclickIndicatorViewToWindowManager() { + injectFakeMouseActionDownEvent(); + + mController.onDestroy(); + + verify(mMockWindowManager).removeView(mController.mAutoclickIndicatorView); + } + + @Test + public void onMotionEvent_initClickSchedulerDelayFromSetting() { + injectFakeMouseActionDownEvent(); + + int delay = + Settings.Secure.getIntForUser( + mTestableContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY, + AccessibilityManager.AUTOCLICK_DELAY_DEFAULT, + mTestableContext.getUserId()); + assertEquals(delay, mController.mClickScheduler.getDelayForTesting()); + } + + @Test + @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void onMotionEvent_flagOn_initCursorAreaSizeFromSetting() { + injectFakeMouseActionDownEvent(); + + int size = + Settings.Secure.getIntForUser( + mTestableContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE, + AccessibilityManager.AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT, + mTestableContext.getUserId()); + assertEquals(size, mController.mAutoclickIndicatorView.getRadiusForTesting()); + } + + @Test + public void onDestroy_clearClickScheduler() { + injectFakeMouseActionDownEvent(); + + mController.onDestroy(); + + assertNull(mController.mClickScheduler); + } + + @Test + public void onDestroy_clearAutoclickSettingsObserver() { + injectFakeMouseActionDownEvent(); + + mController.onDestroy(); + + assertNull(mController.mAutoclickSettingsObserver); + } + + @Test + @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR) + public void onDestroy_flagOn_clearAutoclickIndicatorScheduler() { + injectFakeMouseActionDownEvent(); + + mController.onDestroy(); + + assertNull(mController.mAutoclickIndicatorScheduler); + } + + private void injectFakeMouseActionDownEvent() { + MotionEvent event = getFakeMotionDownEvent(); + event.setSource(InputDevice.SOURCE_MOUSE); + mController.onMotionEvent(event, event, /* policyFlags= */ 0); + } + + private void injectFakeNonMouseActionDownEvent() { + MotionEvent event = getFakeMotionDownEvent(); + event.setSource(InputDevice.SOURCE_KEYBOARD); + mController.onMotionEvent(event, event, /* policyFlags= */ 0); + } + + private MotionEvent getFakeMotionDownEvent() { + return MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 0, + /* action= */ MotionEvent.ACTION_DOWN, + /* x= */ 0, + /* y= */ 0, + /* metaState= */ 0); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 38d3d2fec942..724d7e7c111c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -92,6 +92,7 @@ import android.util.Xml; import android.view.Display; import android.view.DisplayInfo; import android.view.SurfaceControl; +import android.view.WindowInsetsController; import android.window.TaskFragmentOrganizer; import androidx.test.filters.MediumTest; @@ -2109,6 +2110,43 @@ public class TaskTests extends WindowTestsBase { assertEquals(Color.RED, task.getTaskDescription().getBackgroundColor()); } + @Test + public void testUpdateTopOpaqueSystemBarsAppearanceWhenActivityBecomesTransparent() { + final Task task = createTask(mDisplayContent); + final ActivityRecord activity = createActivityRecord(task); + final ActivityManager.TaskDescription td = new ActivityManager.TaskDescription(); + td.setSystemBarsAppearance( + WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND); + activity.setTaskDescription(td); + + assertEquals(WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND, + task.getTaskDescription().getTopOpaqueSystemBarsAppearance()); + + activity.setOccludesParent(false); + + assertEquals(0, task.getTaskDescription().getTopOpaqueSystemBarsAppearance()); + } + + @Test + public void testUpdateTopOpaqueSystemBarsAppearanceWhenActivityBecomesOpaque() { + final Task task = createTask(mDisplayContent); + final ActivityRecord activity = createActivityRecord(task); + activity.setOccludesParent(false); + + final ActivityManager.TaskDescription td = new ActivityManager.TaskDescription(); + td.setSystemBarsAppearance( + WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND); + activity.setTaskDescription(td); + + assertEquals(0, task.getTaskDescription().getTopOpaqueSystemBarsAppearance()); + + activity.setOccludesParent(true); + + assertEquals(WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND, + task.getTaskDescription().getTopOpaqueSystemBarsAppearance()); + + } + private Task getTestTask() { return new TaskBuilder(mSupervisor).setCreateActivity(true).build(); } |