diff options
92 files changed, 2025 insertions, 1018 deletions
diff --git a/apct-tests/perftests/core/Android.bp b/apct-tests/perftests/core/Android.bp index 1e299cdf8002..f16f2caccd49 100644 --- a/apct-tests/perftests/core/Android.bp +++ b/apct-tests/perftests/core/Android.bp @@ -50,6 +50,7 @@ android_test { "junit-params", "core-tests-support", "guava", + "perfetto_trace_java_protos", ], libs: ["android.test.base.stubs.system"], diff --git a/apct-tests/perftests/core/src/android/os/TracePerfTest.java b/apct-tests/perftests/core/src/android/os/TracePerfTest.java index 0d64c390f4c2..bf7c96a3cb85 100644 --- a/apct-tests/perftests/core/src/android/os/TracePerfTest.java +++ b/apct-tests/perftests/core/src/android/os/TracePerfTest.java @@ -17,6 +17,8 @@ package android.os; +import static android.os.PerfettoTrace.Category; + import android.perftests.utils.BenchmarkState; import android.perftests.utils.PerfStatusReporter; import android.perftests.utils.ShellHelper; @@ -31,19 +33,35 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import perfetto.protos.DataSourceConfigOuterClass.DataSourceConfig; +import perfetto.protos.TraceConfigOuterClass.TraceConfig; +import perfetto.protos.TraceConfigOuterClass.TraceConfig.BufferConfig; +import perfetto.protos.TraceConfigOuterClass.TraceConfig.DataSource; +import perfetto.protos.TrackEventConfigOuterClass.TrackEventConfig; + @RunWith(AndroidJUnit4.class) public class TracePerfTest { @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + private static final String FOO = "foo"; + private static final Category FOO_CATEGORY = new Category(FOO); + private static PerfettoTrace.Session sPerfettoSession; + @BeforeClass public static void startTracing() { ShellHelper.runShellCommandRaw("atrace -c --async_start -a *"); + PerfettoTrace.register(false /* isBackendInProcess */); + FOO_CATEGORY.register(); + sPerfettoSession = new PerfettoTrace.Session(false /* isBackendInProcess */, + getTraceConfig(FOO).toByteArray()); } @AfterClass public static void endTracing() { ShellHelper.runShellCommandRaw("atrace --async_stop"); + FOO_CATEGORY.unregister(); + sPerfettoSession.close(); } @Before @@ -84,4 +102,61 @@ public class TracePerfTest { Trace.setCounter("testCounter", 123); } } + + @Test + public void testInstant() { + Trace.instant(Trace.TRACE_TAG_APP, "testInstantA"); + + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + Trace.instant(Trace.TRACE_TAG_APP, "testInstantA"); + } + } + + @Test + public void testInstantPerfetto() { + PerfettoTrace.instant(FOO_CATEGORY, "testInstantP").emit(); + + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + PerfettoTrace.instant(FOO_CATEGORY, "testInstantP").emit(); + } + } + + @Test + public void testInstantPerfettoWithArgs() { + PerfettoTrace.instant(FOO_CATEGORY, "testInstantP") + .addArg("foo", "bar") + .addFlow(1) + .emit(); + + BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + PerfettoTrace.instant(FOO_CATEGORY, "testInstantP") + .addArg("foo", "bar") + .addFlow(1) + .emit(); + } + } + + private static TraceConfig getTraceConfig(String cat) { + BufferConfig bufferConfig = BufferConfig.newBuilder().setSizeKb(1024).build(); + TrackEventConfig trackEventConfig = TrackEventConfig + .newBuilder() + .addEnabledCategories(cat) + .build(); + DataSourceConfig dsConfig = DataSourceConfig + .newBuilder() + .setName("track_event") + .setTargetBuffer(0) + .setTrackEventConfig(trackEventConfig) + .build(); + DataSource ds = DataSource.newBuilder().setConfig(dsConfig).build(); + TraceConfig traceConfig = TraceConfig + .newBuilder() + .addBuffers(bufferConfig) + .addDataSources(ds) + .build(); + return traceConfig; + } } diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java index cd48047bccb4..af40188c4eba 100644 --- a/core/java/android/hardware/input/InputSettings.java +++ b/core/java/android/hardware/input/InputSettings.java @@ -35,7 +35,6 @@ import static com.android.hardware.input.Flags.touchpadVisualizer; import static com.android.hardware.input.Flags.useKeyGestureEventHandler; import static com.android.hardware.input.Flags.useKeyGestureEventHandlerMultiKeyGestures; import static com.android.input.flags.Flags.FLAG_KEYBOARD_REPEAT_KEYS; -import static com.android.input.flags.Flags.enableInputFilterRustImpl; import static com.android.input.flags.Flags.keyboardRepeatKeys; import android.Manifest; @@ -883,7 +882,7 @@ public class InputSettings { * @hide */ public static boolean isAccessibilityBounceKeysFeatureEnabled() { - return keyboardA11yBounceKeysFlag() && enableInputFilterRustImpl(); + return keyboardA11yBounceKeysFlag(); } /** @@ -967,7 +966,7 @@ public class InputSettings { * @hide */ public static boolean isAccessibilitySlowKeysFeatureFlagEnabled() { - return keyboardA11ySlowKeysFlag() && enableInputFilterRustImpl(); + return keyboardA11ySlowKeysFlag(); } /** @@ -1053,7 +1052,7 @@ public class InputSettings { * @hide */ public static boolean isAccessibilityStickyKeysFeatureEnabled() { - return keyboardA11yStickyKeysFlag() && enableInputFilterRustImpl(); + return keyboardA11yStickyKeysFlag(); } /** diff --git a/core/java/android/os/PerfettoTrace.java b/core/java/android/os/PerfettoTrace.java index e3f251e34b45..68f1570154ff 100644 --- a/core/java/android/os/PerfettoTrace.java +++ b/core/java/android/os/PerfettoTrace.java @@ -154,14 +154,44 @@ public final class PerfettoTrace { } } + /** + * Manages a perfetto tracing session. + * Constructing this object with a config automatically starts a tracing session. Each session + * must be closed after use and then the resulting trace bytes can be read. + * + * The session could be in process or system wide, depending on {@code isBackendInProcess}. + * This functionality is intended for testing. + */ + public static final class Session { + private final long mPtr; + + /** + * Session ctor. + */ + public Session(boolean isBackendInProcess, byte[] config) { + mPtr = native_start_session(isBackendInProcess, config); + } + + /** + * Closes the session and returns the trace. + */ + public byte[] close() { + return native_stop_session(mPtr); + } + } + @CriticalNative private static native long native_get_process_track_uuid(); - @CriticalNative private static native long native_get_thread_track_uuid(long tid); @FastNative private static native void native_activate_trigger(String name, int ttlMs); + @FastNative + private static native void native_register(boolean isBackendInProcess); + + private static native long native_start_session(boolean isBackendInProcess, byte[] config); + private static native byte[] native_stop_session(long ptr); /** * Writes a trace message to indicate a given section of code was invoked. @@ -307,7 +337,7 @@ public final class PerfettoTrace { /** * Registers the process with Perfetto. */ - public static void register() { - Trace.registerWithPerfetto(); + public static void register(boolean isBackendInProcess) { + native_register(isBackendInProcess); } } diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 2a5666cbe83c..e769abec7dd9 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -624,6 +624,7 @@ public final class PowerManager { WAKE_REASON_TAP, WAKE_REASON_LIFT, WAKE_REASON_BIOMETRIC, + WAKE_REASON_DOCK, }) @Retention(RetentionPolicy.SOURCE) public @interface WakeReason{} @@ -765,6 +766,12 @@ public final class PowerManager { public static final int WAKE_REASON_BIOMETRIC = 17; /** + * Wake up reason code: Waking up due to a user docking the device. + * @hide + */ + public static final int WAKE_REASON_DOCK = 18; + + /** * Convert the wake reason to a string for debugging purposes. * @hide */ @@ -788,6 +795,7 @@ public final class PowerManager { case WAKE_REASON_TAP: return "WAKE_REASON_TAP"; case WAKE_REASON_LIFT: return "WAKE_REASON_LIFT"; case WAKE_REASON_BIOMETRIC: return "WAKE_REASON_BIOMETRIC"; + case WAKE_REASON_DOCK: return "WAKE_REASON_DOCK"; default: return Integer.toString(wakeReason); } } diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java index 4a37e0a70443..09e6a45dc294 100644 --- a/core/java/android/os/Trace.java +++ b/core/java/android/os/Trace.java @@ -164,8 +164,6 @@ public final class Trace { private static native void nativeInstant(long tag, String name); @FastNative private static native void nativeInstantForTrack(long tag, String trackName, String name); - @FastNative - private static native void nativeRegisterWithPerfetto(); private Trace() { } @@ -545,6 +543,6 @@ public final class Trace { * @hide */ public static void registerWithPerfetto() { - nativeRegisterWithPerfetto(); + PerfettoTrace.register(false /* isBackendInProcess */); } } diff --git a/core/jni/android_os_PerfettoTrace.cpp b/core/jni/android_os_PerfettoTrace.cpp index 962aefc482e4..9bedfa27fa1a 100644 --- a/core/jni/android_os_PerfettoTrace.cpp +++ b/core/jni/android_os_PerfettoTrace.cpp @@ -24,9 +24,12 @@ #include <nativehelper/scoped_primitive_array.h> #include <nativehelper/scoped_utf_chars.h> #include <nativehelper/utils.h> +#include <tracing_perfetto.h> #include <tracing_sdk.h> namespace android { +constexpr int kFlushTimeoutMs = 5000; + template <typename T> inline static T* toPointer(jlong ptr) { return reinterpret_cast<T*>(static_cast<uintptr_t>(ptr)); @@ -51,6 +54,10 @@ static void android_os_PerfettoTrace_activate_trigger(JNIEnv* env, jclass, jstri tracing_perfetto::activate_trigger(name_chars.c_str(), static_cast<uint32_t>(ttl_ms)); } +void android_os_PerfettoTrace_register(bool is_backend_in_process) { + tracing_perfetto::registerWithPerfetto(is_backend_in_process); +} + static jlong android_os_PerfettoTraceCategory_init(JNIEnv* env, jclass, jstring name, jstring tag, jstring severity) { ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name); @@ -85,6 +92,36 @@ static jlong android_os_PerfettoTraceCategory_get_extra_ptr(jlong ptr) { return toJLong(category->get()); } +static jlong android_os_PerfettoTrace_start_session(JNIEnv* env, jclass /* obj */, + jboolean is_backend_in_process, + jbyteArray config_bytes) { + jsize length = env->GetArrayLength(config_bytes); + std::vector<uint8_t> data; + data.reserve(length); + env->GetByteArrayRegion(config_bytes, 0, length, reinterpret_cast<jbyte*>(data.data())); + + tracing_perfetto::Session* session = + new tracing_perfetto::Session(is_backend_in_process, data.data(), length); + + return reinterpret_cast<long>(session); +} + +static jbyteArray android_os_PerfettoTrace_stop_session([[maybe_unused]] JNIEnv* env, + jclass /* obj */, jlong ptr) { + tracing_perfetto::Session* session = reinterpret_cast<tracing_perfetto::Session*>(ptr); + + session->FlushBlocking(kFlushTimeoutMs); + session->StopBlocking(); + + std::vector<uint8_t> data = session->ReadBlocking(); + + delete session; + + jbyteArray bytes = env->NewByteArray(data.size()); + env->SetByteArrayRegion(bytes, 0, data.size(), reinterpret_cast<jbyte*>(data.data())); + return bytes; +} + static const JNINativeMethod gCategoryMethods[] = { {"native_init", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)J", (void*)android_os_PerfettoTraceCategory_init}, @@ -101,7 +138,10 @@ static const JNINativeMethod gTraceMethods[] = {"native_get_thread_track_uuid", "(J)J", (void*)android_os_PerfettoTrace_get_thread_track_uuid}, {"native_activate_trigger", "(Ljava/lang/String;I)V", - (void*)android_os_PerfettoTrace_activate_trigger}}; + (void*)android_os_PerfettoTrace_activate_trigger}, + {"native_register", "(Z)V", (void*)android_os_PerfettoTrace_register}, + {"native_start_session", "(Z[B)J", (void*)android_os_PerfettoTrace_start_session}, + {"native_stop_session", "(J)[B", (void*)android_os_PerfettoTrace_stop_session}}; int register_android_os_PerfettoTrace(JNIEnv* env) { int res = jniRegisterNativeMethods(env, "android/os/PerfettoTrace", gTraceMethods, diff --git a/core/jni/android_os_Trace.cpp b/core/jni/android_os_Trace.cpp index 21e056dfa12b..50618c5a07af 100644 --- a/core/jni/android_os_Trace.cpp +++ b/core/jni/android_os_Trace.cpp @@ -131,10 +131,6 @@ static jboolean android_os_Trace_nativeIsTagEnabled(jlong tag) { return tracing_perfetto::isTagEnabled(tag); } -static void android_os_Trace_nativeRegisterWithPerfetto(JNIEnv* env) { - tracing_perfetto::registerWithPerfetto(); -} - static const JNINativeMethod gTraceMethods[] = { /* name, signature, funcPtr */ {"nativeSetAppTracingAllowed", "(Z)V", (void*)android_os_Trace_nativeSetAppTracingAllowed}, @@ -157,7 +153,6 @@ static const JNINativeMethod gTraceMethods[] = { {"nativeInstant", "(JLjava/lang/String;)V", (void*)android_os_Trace_nativeInstant}, {"nativeInstantForTrack", "(JLjava/lang/String;Ljava/lang/String;)V", (void*)android_os_Trace_nativeInstantForTrack}, - {"nativeRegisterWithPerfetto", "()V", (void*)android_os_Trace_nativeRegisterWithPerfetto}, // ----------- @CriticalNative ---------------- {"nativeIsTagEnabled", "(J)Z", (void*)android_os_Trace_nativeIsTagEnabled}, diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 1b6746ca4b63..c06ad64cc0f5 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -122,7 +122,6 @@ android_test { "android.view.flags-aconfig-java", ], jni_libs: [ - "libperfetto_trace_test_jni", "libpowermanagertest_jni", "libviewRootImplTest_jni", "libworksourceparceltest_jni", diff --git a/core/tests/coretests/jni/Android.bp b/core/tests/coretests/jni/Android.bp index 798ec90eb884..d6379ca8c3e6 100644 --- a/core/tests/coretests/jni/Android.bp +++ b/core/tests/coretests/jni/Android.bp @@ -111,27 +111,3 @@ cc_test_library { ], gtest: false, } - -cc_test_library { - name: "libperfetto_trace_test_jni", - srcs: [ - "PerfettoTraceTest.cpp", - ], - static_libs: [ - "perfetto_trace_protos", - "libtracing_perfetto_test_utils", - ], - shared_libs: [ - "liblog", - "libnativehelper", - "libperfetto_c", - "libprotobuf-cpp-lite", - "libtracing_perfetto", - ], - stl: "libc++_static", - cflags: [ - "-Werror", - "-Wall", - ], - gtest: false, -} diff --git a/core/tests/coretests/jni/PerfettoTraceTest.cpp b/core/tests/coretests/jni/PerfettoTraceTest.cpp deleted file mode 100644 index 41d02ed70c9a..000000000000 --- a/core/tests/coretests/jni/PerfettoTraceTest.cpp +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// #define LOG_NDEBUG 0 -#define LOG_TAG "PerfettoTraceTest" - -#include <nativehelper/JNIHelp.h> -#include <utils/Log.h> - -#include "jni.h" -#include "perfetto/public/abi/data_source_abi.h" -#include "perfetto/public/abi/heap_buffer.h" -#include "perfetto/public/abi/pb_decoder_abi.h" -#include "perfetto/public/abi/tracing_session_abi.h" -#include "perfetto/public/abi/track_event_abi.h" -#include "perfetto/public/compiler.h" -#include "perfetto/public/data_source.h" -#include "perfetto/public/pb_decoder.h" -#include "perfetto/public/producer.h" -#include "perfetto/public/protos/config/trace_config.pzc.h" -#include "perfetto/public/protos/trace/interned_data/interned_data.pzc.h" -#include "perfetto/public/protos/trace/test_event.pzc.h" -#include "perfetto/public/protos/trace/trace.pzc.h" -#include "perfetto/public/protos/trace/trace_packet.pzc.h" -#include "perfetto/public/protos/trace/track_event/debug_annotation.pzc.h" -#include "perfetto/public/protos/trace/track_event/track_descriptor.pzc.h" -#include "perfetto/public/protos/trace/track_event/track_event.pzc.h" -#include "perfetto/public/protos/trace/trigger.pzc.h" -#include "perfetto/public/te_category_macros.h" -#include "perfetto/public/te_macros.h" -#include "perfetto/public/track_event.h" -#include "protos/perfetto/trace/interned_data/interned_data.pb.h" -#include "protos/perfetto/trace/trace.pb.h" -#include "protos/perfetto/trace/trace_packet.pb.h" -#include "tracing_perfetto.h" -#include "utils.h" - -namespace android { -using ::perfetto::protos::EventCategory; -using ::perfetto::protos::EventName; -using ::perfetto::protos::FtraceEvent; -using ::perfetto::protos::FtraceEventBundle; -using ::perfetto::protos::InternedData; -using ::perfetto::protos::Trace; -using ::perfetto::protos::TracePacket; - -using ::perfetto::shlib::test_utils::TracingSession; - -struct TracingSessionHolder { - TracingSession tracing_session; -}; - -static void nativeRegisterPerfetto([[maybe_unused]] JNIEnv* env, jclass /* obj */) { - tracing_perfetto::registerWithPerfetto(false /* test */); -} - -static jlong nativeStartTracing(JNIEnv* env, jclass /* obj */, jbyteArray configBytes) { - jsize length = env->GetArrayLength(configBytes); - std::vector<uint8_t> data; - data.reserve(length); - env->GetByteArrayRegion(configBytes, 0, length, reinterpret_cast<jbyte*>(data.data())); - - TracingSession session = TracingSession::FromBytes(data.data(), length); - TracingSessionHolder* holder = new TracingSessionHolder(std::move(session)); - - return reinterpret_cast<long>(holder); -} - -static jbyteArray nativeStopTracing([[maybe_unused]] JNIEnv* env, jclass /* obj */, jlong ptr) { - TracingSessionHolder* holder = reinterpret_cast<TracingSessionHolder*>(ptr); - - // Stop - holder->tracing_session.FlushBlocking(5000); - holder->tracing_session.StopBlocking(); - - std::vector<uint8_t> data = holder->tracing_session.ReadBlocking(); - - delete holder; - - jbyteArray bytes = env->NewByteArray(data.size()); - env->SetByteArrayRegion(bytes, 0, data.size(), reinterpret_cast<jbyte*>(data.data())); - return bytes; -} - -extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { - JNIEnv* env; - const JNINativeMethod methodTable[] = {/* name, signature, funcPtr */ - {"nativeStartTracing", "([B)J", - (void*)nativeStartTracing}, - {"nativeStopTracing", "(J)[B", (void*)nativeStopTracing}, - {"nativeRegisterPerfetto", "()V", - (void*)nativeRegisterPerfetto}}; - - if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { - return JNI_ERR; - } - - jniRegisterNativeMethods(env, "android/os/PerfettoTraceTest", methodTable, - sizeof(methodTable) / sizeof(JNINativeMethod)); - - return JNI_VERSION_1_6; -} - -} /* namespace android */ diff --git a/core/tests/coretests/src/android/os/PerfettoTraceTest.java b/core/tests/coretests/src/android/os/PerfettoTraceTest.java index ad28383689af..0b5a44665d2b 100644 --- a/core/tests/coretests/src/android/os/PerfettoTraceTest.java +++ b/core/tests/coretests/src/android/os/PerfettoTraceTest.java @@ -28,7 +28,6 @@ import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.util.ArraySet; -import android.util.Log; import androidx.test.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -84,19 +83,9 @@ public class PerfettoTraceTest { private final Set<String> mDebugAnnotationNames = new ArraySet<>(); private final Set<String> mTrackNames = new ArraySet<>(); - static { - try { - System.loadLibrary("perfetto_trace_test_jni"); - Log.i(TAG, "Successfully loaded trace_test native library"); - } catch (UnsatisfiedLinkError ule) { - Log.w(TAG, "Could not load trace_test native library"); - } - } - @Before public void setUp() { - PerfettoTrace.register(); - nativeRegisterPerfetto(); + PerfettoTrace.register(true); FOO_CATEGORY.register(); mCategoryNames.clear(); @@ -110,7 +99,7 @@ public class PerfettoTraceTest { public void testDebugAnnotations() throws Exception { TraceConfig traceConfig = getTraceConfig(FOO); - long ptr = nativeStartTracing(traceConfig.toByteArray()); + PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray()); PerfettoTrace.instant(FOO_CATEGORY, "event") .addFlow(2) @@ -121,7 +110,7 @@ public class PerfettoTraceTest { .addArg("string_val", FOO) .emit(); - byte[] traceBytes = nativeStopTracing(ptr); + byte[] traceBytes = session.close(); Trace trace = Trace.parseFrom(traceBytes); @@ -165,11 +154,11 @@ public class PerfettoTraceTest { public void testDebugAnnotationsWithLambda() throws Exception { TraceConfig traceConfig = getTraceConfig(FOO); - long ptr = nativeStartTracing(traceConfig.toByteArray()); + PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray()); PerfettoTrace.instant(FOO_CATEGORY, "event").addArg("long_val", 123L).emit(); - byte[] traceBytes = nativeStopTracing(ptr); + byte[] traceBytes = session.close(); Trace trace = Trace.parseFrom(traceBytes); @@ -200,7 +189,7 @@ public class PerfettoTraceTest { public void testNamedTrack() throws Exception { TraceConfig traceConfig = getTraceConfig(FOO); - long ptr = nativeStartTracing(traceConfig.toByteArray()); + PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray()); PerfettoTrace.begin(FOO_CATEGORY, "event") .usingNamedTrack(PerfettoTrace.getProcessTrackUuid(), FOO) @@ -211,7 +200,7 @@ public class PerfettoTraceTest { .usingNamedTrack(PerfettoTrace.getThreadTrackUuid(Process.myTid()), "bar") .emit(); - Trace trace = Trace.parseFrom(nativeStopTracing(ptr)); + Trace trace = Trace.parseFrom(session.close()); boolean hasTrackEvent = false; boolean hasTrackUuid = false; @@ -248,7 +237,7 @@ public class PerfettoTraceTest { public void testProcessThreadNamedTrack() throws Exception { TraceConfig traceConfig = getTraceConfig(FOO); - long ptr = nativeStartTracing(traceConfig.toByteArray()); + PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray()); PerfettoTrace.begin(FOO_CATEGORY, "event") .usingProcessNamedTrack(FOO) @@ -259,7 +248,7 @@ public class PerfettoTraceTest { .usingThreadNamedTrack(Process.myTid(), "%s-%s", "bar", "stool") .emit(); - Trace trace = Trace.parseFrom(nativeStopTracing(ptr)); + Trace trace = Trace.parseFrom(session.close()); boolean hasTrackEvent = false; boolean hasTrackUuid = false; @@ -296,13 +285,13 @@ public class PerfettoTraceTest { public void testCounterSimple() throws Exception { TraceConfig traceConfig = getTraceConfig(FOO); - long ptr = nativeStartTracing(traceConfig.toByteArray()); + PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray()); PerfettoTrace.counter(FOO_CATEGORY, 16, FOO).emit(); PerfettoTrace.counter(FOO_CATEGORY, 3.14, "bar").emit(); - Trace trace = Trace.parseFrom(nativeStopTracing(ptr)); + Trace trace = Trace.parseFrom(session.close()); boolean hasTrackEvent = false; boolean hasCounterValue = false; @@ -339,7 +328,7 @@ public class PerfettoTraceTest { public void testCounter() throws Exception { TraceConfig traceConfig = getTraceConfig(FOO); - long ptr = nativeStartTracing(traceConfig.toByteArray()); + PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray()); PerfettoTrace.counter(FOO_CATEGORY, 16) .usingCounterTrack(PerfettoTrace.getProcessTrackUuid(), FOO).emit(); @@ -348,7 +337,7 @@ public class PerfettoTraceTest { .usingCounterTrack(PerfettoTrace.getThreadTrackUuid(Process.myTid()), "%s-%s", "bar", "stool").emit(); - Trace trace = Trace.parseFrom(nativeStopTracing(ptr)); + Trace trace = Trace.parseFrom(session.close()); boolean hasTrackEvent = false; boolean hasCounterValue = false; @@ -385,14 +374,14 @@ public class PerfettoTraceTest { public void testProcessThreadCounter() throws Exception { TraceConfig traceConfig = getTraceConfig(FOO); - long ptr = nativeStartTracing(traceConfig.toByteArray()); + PerfettoTrace.Session session = new PerfettoTrace.Session(true, 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)); + Trace trace = Trace.parseFrom(session.close()); boolean hasTrackEvent = false; boolean hasCounterValue = false; @@ -429,7 +418,7 @@ public class PerfettoTraceTest { public void testProto() throws Exception { TraceConfig traceConfig = getTraceConfig(FOO); - long ptr = nativeStartTracing(traceConfig.toByteArray()); + PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray()); PerfettoTrace.instant(FOO_CATEGORY, "event_proto") .beginProto() @@ -441,7 +430,7 @@ public class PerfettoTraceTest { .endProto() .emit(); - byte[] traceBytes = nativeStopTracing(ptr); + byte[] traceBytes = session.close(); Trace trace = Trace.parseFrom(traceBytes); @@ -477,7 +466,7 @@ public class PerfettoTraceTest { public void testProtoNested() throws Exception { TraceConfig traceConfig = getTraceConfig(FOO); - long ptr = nativeStartTracing(traceConfig.toByteArray()); + PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray()); PerfettoTrace.instant(FOO_CATEGORY, "event_proto_nested") .beginProto() @@ -494,7 +483,7 @@ public class PerfettoTraceTest { .endProto() .emit(); - byte[] traceBytes = nativeStopTracing(ptr); + byte[] traceBytes = session.close(); Trace trace = Trace.parseFrom(traceBytes); @@ -538,13 +527,13 @@ public class PerfettoTraceTest { public void testActivateTrigger() throws Exception { TraceConfig traceConfig = getTriggerTraceConfig(FOO, FOO); - long ptr = nativeStartTracing(traceConfig.toByteArray()); + PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray()); PerfettoTrace.instant(FOO_CATEGORY, "event_trigger").emit(); PerfettoTrace.activateTrigger(FOO, 1000); - byte[] traceBytes = nativeStopTracing(ptr); + byte[] traceBytes = session.close(); Trace trace = Trace.parseFrom(traceBytes); @@ -569,7 +558,7 @@ public class PerfettoTraceTest { TraceConfig traceConfig = getTraceConfig(BAR); Category barCategory = new Category(BAR); - long ptr = nativeStartTracing(traceConfig.toByteArray()); + PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray()); PerfettoTrace.instant(barCategory, "event") .addArg("before", 1) @@ -581,7 +570,7 @@ public class PerfettoTraceTest { .addArg("after", 1) .emit(); - byte[] traceBytes = nativeStopTracing(ptr); + byte[] traceBytes = session.close(); Trace trace = Trace.parseFrom(traceBytes); @@ -603,10 +592,6 @@ public class PerfettoTraceTest { assertThat(mDebugAnnotationNames).doesNotContain("before"); } - private static native long nativeStartTracing(byte[] config); - private static native void nativeRegisterPerfetto(); - private static native byte[] nativeStopTracing(long ptr); - private TrackEvent getTrackEvent(Trace trace, int idx) { int curIdx = 0; for (TracePacket packet: trace.getPacketList()) { 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 b09d324833e8..4eaf8049f04f 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 @@ -976,11 +976,13 @@ class DesktopTasksController( cascadeWindow(bounds, displayLayout, displayId) } val pendingIntent = - PendingIntent.getActivity( + PendingIntent.getActivityAsUser( context, /* requestCode= */ 0, intent, PendingIntent.FLAG_IMMUTABLE, + /* options= */ null, + UserHandle.of(userId), ) val ops = ActivityOptions.fromBundle(options).apply { diff --git a/media/java/android/media/OWNERS b/media/java/android/media/OWNERS index b6096a1acd85..a600017119e2 100644 --- a/media/java/android/media/OWNERS +++ b/media/java/android/media/OWNERS @@ -1,6 +1,7 @@ # Bug component: 1344 -fgoldfain@google.com +pshehane@google.com elaurent@google.com +etalvala@google.com lajos@google.com jmtrivi@google.com diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index dace50fafbf1..a66ad19d5bd6 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -196,6 +196,18 @@ flag { } flag { + name: "notification_undo_guts_on_config_changed" + namespace: "systemui" + description: "Fixes a bug where a theme or font change while notification guts were open" + " (e.g. the snooze options or notification info) would show an empty notification by" + " closing the guts and undoing changes." + bug: "379267630" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "pss_app_selector_recents_split_screen" namespace: "systemui" description: "Allows recent apps selected for partial screenshare to be launched in split screen mode" diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt index 30dfa5bb826a..31aebc28d072 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt @@ -156,14 +156,8 @@ constructor( val bottomAreaPlaceable = bottomAreaMeasurable.measure(noMinConstraints) - val screensaverButtonSizeInt = screensaverButtonSize.roundToPx() val screensaverButtonPlaceable = - screensaverButtonMeasurable?.measure( - Constraints.fixed( - width = screensaverButtonSizeInt, - height = screensaverButtonSizeInt, - ) - ) + screensaverButtonMeasurable?.measure(noMinConstraints) val communalGridPlaceable = communalGridMeasurable.measure( @@ -181,12 +175,12 @@ constructor( screensaverButtonPlaceable?.place( x = constraints.maxWidth - - screensaverButtonSizeInt - - screensaverButtonPaddingInt, + screensaverButtonPaddingInt - + screensaverButtonPlaceable.width, y = constraints.maxHeight - - screensaverButtonSizeInt - - screensaverButtonPaddingInt, + screensaverButtonPaddingInt - + screensaverButtonPlaceable.height, ) } } @@ -194,7 +188,6 @@ constructor( } companion object { - private val screensaverButtonSize: Dp = 64.dp private val screensaverButtonPadding: Dp = 24.dp // TODO(b/382739998): Remove these hardcoded values once lock icon size and bottom area diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalToDreamButtonSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalToDreamButtonSection.kt index 9421596f7116..13d551aef4c2 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalToDreamButtonSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalToDreamButtonSection.kt @@ -16,14 +16,37 @@ package com.android.systemui.communal.ui.compose.section +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.shape.CornerSize import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.RoundRect +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.geometry.toRect +import androidx.compose.ui.graphics.Outline +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.res.stringResource -import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp import com.android.compose.PlatformIconButton import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor +import com.android.systemui.communal.ui.compose.extensions.observeTaps import com.android.systemui.communal.ui.viewmodel.CommunalToDreamButtonViewModel import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.res.R @@ -43,23 +66,111 @@ constructor( val viewModel = rememberViewModel("CommunalToDreamButtonSection") { viewModelFactory.create() } - val shouldShowDreamButtonOnHub by - viewModel.shouldShowDreamButtonOnHub.collectAsStateWithLifecycle(false) - if (!shouldShowDreamButtonOnHub) { + if (!viewModel.shouldShowDreamButtonOnHub) { return } - PlatformIconButton( - onClick = { viewModel.onShowDreamButtonTap() }, - iconResource = R.drawable.ic_screensaver_auto, - contentDescription = - stringResource(R.string.accessibility_glanceable_hub_to_dream_button), - colors = - IconButtonDefaults.filledIconButtonColors( - contentColor = MaterialTheme.colorScheme.onPrimaryContainer, - containerColor = MaterialTheme.colorScheme.primaryContainer, - ), + if (viewModel.shouldShowTooltip) { + Column( + modifier = + Modifier.widthIn(max = tooltipMaxWidth).pointerInput(Unit) { + observeTaps { viewModel.setDreamButtonTooltipDismissed() } + } + ) { + Tooltip( + pointerOffsetDp = buttonSize.div(2), + text = stringResource(R.string.glanceable_hub_to_dream_button_tooltip), + ) + GoToDreamButton( + modifier = Modifier.width(buttonSize).height(buttonSize).align(Alignment.End) + ) { + viewModel.onShowDreamButtonTap() + } + } + } else { + GoToDreamButton(modifier = Modifier.width(buttonSize).height(buttonSize)) { + viewModel.onShowDreamButtonTap() + } + } + } + + companion object { + private val buttonSize = 64.dp + private val tooltipMaxWidth = 350.dp + } +} + +@Composable +private fun GoToDreamButton(modifier: Modifier, onClick: () -> Unit) { + PlatformIconButton( + modifier = modifier, + onClick = onClick, + iconResource = R.drawable.ic_screensaver_auto, + contentDescription = stringResource(R.string.accessibility_glanceable_hub_to_dream_button), + colors = + IconButtonDefaults.filledIconButtonColors( + contentColor = MaterialTheme.colorScheme.onPrimaryContainer, + containerColor = MaterialTheme.colorScheme.primaryContainer, + ), + ) +} + +@Composable +private fun Tooltip(pointerOffsetDp: Dp, text: String) { + Surface( + color = MaterialTheme.colorScheme.surface, + shape = TooltipShape(pointerSizeDp = 12.dp, pointerOffsetDp = pointerOffsetDp), + ) { + Text( + modifier = Modifier.padding(start = 32.dp, top = 16.dp, end = 32.dp, bottom = 32.dp), + color = MaterialTheme.colorScheme.onSurface, + text = text, ) } + + Spacer(modifier = Modifier.height(4.dp)) +} + +private class TooltipShape(private val pointerSizeDp: Dp, private val pointerOffsetDp: Dp) : Shape { + + override fun createOutline( + size: Size, + layoutDirection: LayoutDirection, + density: Density, + ): Outline { + + val pointerSizePx = with(density) { pointerSizeDp.toPx() } + val pointerOffsetPx = with(density) { pointerOffsetDp.toPx() } + val cornerRadius = CornerRadius(CornerSize(16.dp).toPx(size, density)) + val bubbleSize = size.copy(height = size.height - pointerSizePx) + + val path = + Path().apply { + addRoundRect( + RoundRect( + rect = bubbleSize.toRect(), + topLeft = cornerRadius, + topRight = cornerRadius, + bottomRight = cornerRadius, + bottomLeft = cornerRadius, + ) + ) + addPath( + Path().apply { + moveTo(0f, 0f) + lineTo(pointerSizePx / 2f, pointerSizePx) + lineTo(pointerSizePx, 0f) + close() + }, + offset = + Offset( + x = bubbleSize.width - pointerOffsetPx - pointerSizePx / 2f, + y = bubbleSize.height, + ), + ) + } + + return Outline.Generic(path) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt index 0d410cff5ff6..b6359c7f8da5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt @@ -116,6 +116,34 @@ class CommunalPrefsRepositoryImplTest : SysuiTestCase() { } @Test + fun isDreamButtonTooltipDismissedValue_byDefault_isFalse() = + testScope.runTest { + val isDreamButtonTooltipDismissed by + collectLastValue(underTest.isDreamButtonTooltipDismissed(MAIN_USER)) + assertThat(isDreamButtonTooltipDismissed).isFalse() + } + + @Test + fun isDreamButtonTooltipDismissedValue_onSet_isTrue() = + testScope.runTest { + val isDreamButtonTooltipDismissed by + collectLastValue(underTest.isDreamButtonTooltipDismissed(MAIN_USER)) + + underTest.setDreamButtonTooltipDismissed(MAIN_USER) + assertThat(isDreamButtonTooltipDismissed).isTrue() + } + + @Test + fun isDreamButtonTooltipDismissedValue_onSetForDifferentUser_isStillFalse() = + testScope.runTest { + val isDreamButtonTooltipDismissed by + collectLastValue(underTest.isDreamButtonTooltipDismissed(MAIN_USER)) + + underTest.setDreamButtonTooltipDismissed(SECONDARY_USER) + assertThat(isDreamButtonTooltipDismissed).isFalse() + } + + @Test fun getSharedPreferences_whenFileRestored() = testScope.runTest { val isCtaDismissed by collectLastValue(underTest.isCtaDismissed(MAIN_USER)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt index 809099e0d464..eb1f1d9c52f4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt @@ -38,19 +38,19 @@ import com.android.systemui.communal.data.model.DisabledReason import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl.Companion.GLANCEABLE_HUB_BACKGROUND_SETTING import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled import com.android.systemui.communal.shared.model.CommunalBackgroundType -import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.communal.shared.model.WhenToDream import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest -import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.testKosmos import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.fakeSettings import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage -import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -62,9 +62,11 @@ import platform.test.runner.parameterized.Parameters @RunWith(ParameterizedAndroidJunit4::class) class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiTestCase() { private val kosmos = - testKosmos().apply { mainResources = mContext.orCreateTestableResources.resources } - private val testScope = kosmos.testScope - private lateinit var underTest: CommunalSettingsRepository + testKosmos() + .apply { mainResources = mContext.orCreateTestableResources.resources } + .useUnconfinedTestDispatcher() + + private val Kosmos.underTest by Kosmos.Fixture { communalSettingsRepository } init { mSetFlagsRule.setFlagsParameterization(flags!!) @@ -76,98 +78,105 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_FEATURES_NONE) setKeyguardFeaturesDisabled(SECONDARY_USER, KEYGUARD_DISABLE_FEATURES_NONE) setKeyguardFeaturesDisabled(WORK_PROFILE, KEYGUARD_DISABLE_FEATURES_NONE) - underTest = kosmos.communalSettingsRepository } @EnableFlags(FLAG_COMMUNAL_HUB) @DisableFlags(FLAG_GLANCEABLE_HUB_V2) @Test - fun getFlagEnabled_bothEnabled() { - kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true) + fun getFlagEnabled_bothEnabled() = + kosmos.runTest { + fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true) - assertThat(underTest.getFlagEnabled()).isTrue() - } + assertThat(underTest.getFlagEnabled()).isTrue() + } @DisableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2) @Test - fun getFlagEnabled_bothDisabled() { - kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false) + fun getFlagEnabled_bothDisabled() = + kosmos.runTest { + fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false) - assertThat(underTest.getFlagEnabled()).isFalse() - } + assertThat(underTest.getFlagEnabled()).isFalse() + } @DisableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2) @Test - fun getFlagEnabled_onlyClassicFlagEnabled() { - kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true) + fun getFlagEnabled_onlyClassicFlagEnabled() = + kosmos.runTest { + fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true) - assertThat(underTest.getFlagEnabled()).isFalse() - } + assertThat(underTest.getFlagEnabled()).isFalse() + } @EnableFlags(FLAG_COMMUNAL_HUB) @DisableFlags(FLAG_GLANCEABLE_HUB_V2) @Test - fun getFlagEnabled_onlyTrunkFlagEnabled() { - kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false) + fun getFlagEnabled_onlyTrunkFlagEnabled() = + kosmos.runTest { + fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false) - assertThat(underTest.getFlagEnabled()).isFalse() - } + assertThat(underTest.getFlagEnabled()).isFalse() + } @EnableFlags(FLAG_GLANCEABLE_HUB_V2) @DisableFlags(FLAG_COMMUNAL_HUB) @Test - fun getFlagEnabled_mobileConfigEnabled() { - mContext.orCreateTestableResources.addOverride( - com.android.internal.R.bool.config_glanceableHubEnabled, - true, - ) + fun getFlagEnabled_mobileConfigEnabled() = + kosmos.runTest { + mContext.orCreateTestableResources.addOverride( + com.android.internal.R.bool.config_glanceableHubEnabled, + true, + ) - assertThat(underTest.getFlagEnabled()).isTrue() - } + assertThat(underTest.getFlagEnabled()).isTrue() + } @DisableFlags(FLAG_GLANCEABLE_HUB_V2, FLAG_COMMUNAL_HUB) @Test - fun getFlagEnabled_onlyMobileConfigEnabled() { - mContext.orCreateTestableResources.addOverride( - com.android.internal.R.bool.config_glanceableHubEnabled, - true, - ) + fun getFlagEnabled_onlyMobileConfigEnabled() = + kosmos.runTest { + mContext.orCreateTestableResources.addOverride( + com.android.internal.R.bool.config_glanceableHubEnabled, + true, + ) - assertThat(underTest.getFlagEnabled()).isFalse() - } + assertThat(underTest.getFlagEnabled()).isFalse() + } @EnableFlags(FLAG_GLANCEABLE_HUB_V2) @DisableFlags(FLAG_COMMUNAL_HUB) @Test - fun getFlagEnabled_onlyMobileFlagEnabled() { - mContext.orCreateTestableResources.addOverride( - com.android.internal.R.bool.config_glanceableHubEnabled, - false, - ) + fun getFlagEnabled_onlyMobileFlagEnabled() = + kosmos.runTest { + mContext.orCreateTestableResources.addOverride( + com.android.internal.R.bool.config_glanceableHubEnabled, + false, + ) - assertThat(underTest.getFlagEnabled()).isFalse() - } + assertThat(underTest.getFlagEnabled()).isFalse() + } @EnableFlags(FLAG_GLANCEABLE_HUB_V2) @DisableFlags(FLAG_COMMUNAL_HUB) @Test - fun getFlagEnabled_oldFlagIgnored() { - // New config flag enabled. - mContext.orCreateTestableResources.addOverride( - com.android.internal.R.bool.config_glanceableHubEnabled, - true, - ) + fun getFlagEnabled_oldFlagIgnored() = + kosmos.runTest { + // New config flag enabled. + mContext.orCreateTestableResources.addOverride( + com.android.internal.R.bool.config_glanceableHubEnabled, + true, + ) - // Old config flag disabled. - kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false) + // Old config flag disabled. + fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false) - assertThat(underTest.getFlagEnabled()).isTrue() - } + assertThat(underTest.getFlagEnabled()).isTrue() + } @EnableFlags(FLAG_COMMUNAL_HUB) @Test fun secondaryUserIsInvalid() = - testScope.runTest { + kosmos.runTest { val enabledState by collectLastValue(underTest.getEnabledState(SECONDARY_USER)) assertThat(enabledState?.enabled).isFalse() @@ -187,7 +196,7 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT @DisableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2) @Test fun communalHubFlagIsDisabled() = - testScope.runTest { + kosmos.runTest { val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER)) assertThat(enabledState?.enabled).isFalse() assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_FLAG) @@ -196,35 +205,23 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT @EnableFlags(FLAG_COMMUNAL_HUB) @Test fun hubIsDisabledByUser() = - testScope.runTest { - kosmos.fakeSettings.putIntForUser( - Settings.Secure.GLANCEABLE_HUB_ENABLED, - 0, - PRIMARY_USER.id, - ) + kosmos.runTest { + fakeSettings.putIntForUser(Settings.Secure.GLANCEABLE_HUB_ENABLED, 0, PRIMARY_USER.id) val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER)) assertThat(enabledState?.enabled).isFalse() assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_USER_SETTING) - kosmos.fakeSettings.putIntForUser( - Settings.Secure.GLANCEABLE_HUB_ENABLED, - 1, - SECONDARY_USER.id, - ) + fakeSettings.putIntForUser(Settings.Secure.GLANCEABLE_HUB_ENABLED, 1, SECONDARY_USER.id) assertThat(enabledState?.enabled).isFalse() - kosmos.fakeSettings.putIntForUser( - Settings.Secure.GLANCEABLE_HUB_ENABLED, - 1, - PRIMARY_USER.id, - ) + fakeSettings.putIntForUser(Settings.Secure.GLANCEABLE_HUB_ENABLED, 1, PRIMARY_USER.id) assertThat(enabledState?.enabled).isTrue() } @EnableFlags(FLAG_COMMUNAL_HUB) @Test fun hubIsDisabledByDevicePolicy() = - testScope.runTest { + kosmos.runTest { val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER)) assertThat(enabledState?.enabled).isTrue() @@ -236,7 +233,7 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT @EnableFlags(FLAG_COMMUNAL_HUB) @Test fun widgetsAllowedForWorkProfile_isFalse_whenDisallowedByDevicePolicy() = - testScope.runTest { + kosmos.runTest { val widgetsAllowedForWorkProfile by collectLastValue(underTest.getAllowedByDevicePolicy(WORK_PROFILE)) assertThat(widgetsAllowedForWorkProfile).isTrue() @@ -248,7 +245,7 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT @EnableFlags(FLAG_COMMUNAL_HUB) @Test fun hubIsEnabled_whenDisallowedByDevicePolicyForWorkProfile() = - testScope.runTest { + kosmos.runTest { val enabledStateForPrimaryUser by collectLastValue(underTest.getEnabledState(PRIMARY_USER)) assertThat(enabledStateForPrimaryUser?.enabled).isTrue() @@ -260,15 +257,11 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT @EnableFlags(FLAG_COMMUNAL_HUB) @Test fun hubIsDisabledByUserAndDevicePolicy() = - testScope.runTest { + kosmos.runTest { val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER)) assertThat(enabledState?.enabled).isTrue() - kosmos.fakeSettings.putIntForUser( - Settings.Secure.GLANCEABLE_HUB_ENABLED, - 0, - PRIMARY_USER.id, - ) + fakeSettings.putIntForUser(Settings.Secure.GLANCEABLE_HUB_ENABLED, 0, PRIMARY_USER.id) setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_WIDGETS_ALL) assertThat(enabledState?.enabled).isFalse() @@ -282,17 +275,17 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT @Test @DisableFlags(FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND) fun backgroundType_defaultValue() = - testScope.runTest { + kosmos.runTest { val backgroundType by collectLastValue(underTest.getBackground(PRIMARY_USER)) assertThat(backgroundType).isEqualTo(CommunalBackgroundType.ANIMATED) } @Test fun backgroundType_verifyAllValues() = - testScope.runTest { + kosmos.runTest { val backgroundType by collectLastValue(underTest.getBackground(PRIMARY_USER)) for (type in CommunalBackgroundType.entries) { - kosmos.fakeSettings.putIntForUser( + fakeSettings.putIntForUser( GLANCEABLE_HUB_BACKGROUND_SETTING, type.value, PRIMARY_USER.id, @@ -308,30 +301,71 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT @Test fun screensaverDisabledByUser() = - testScope.runTest { + kosmos.runTest { val enabledState by collectLastValue(underTest.getScreensaverEnabledState(PRIMARY_USER)) - kosmos.fakeSettings.putIntForUser( - Settings.Secure.SCREENSAVER_ENABLED, - 0, - PRIMARY_USER.id, - ) + fakeSettings.putIntForUser(Settings.Secure.SCREENSAVER_ENABLED, 0, PRIMARY_USER.id) assertThat(enabledState).isFalse() } @Test fun screensaverEnabledByUser() = - testScope.runTest { + kosmos.runTest { val enabledState by collectLastValue(underTest.getScreensaverEnabledState(PRIMARY_USER)) - kosmos.fakeSettings.putIntForUser( - Settings.Secure.SCREENSAVER_ENABLED, + fakeSettings.putIntForUser(Settings.Secure.SCREENSAVER_ENABLED, 1, PRIMARY_USER.id) + + assertThat(enabledState).isTrue() + } + + @Test + fun whenToDream_charging() = + kosmos.runTest { + val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER)) + + fakeSettings.putIntForUser( + Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1, PRIMARY_USER.id, ) - assertThat(enabledState).isTrue() + assertThat(whenToDreamState).isEqualTo(WhenToDream.WHILE_CHARGING) + } + + @Test + fun whenToDream_docked() = + kosmos.runTest { + val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER)) + + fakeSettings.putIntForUser( + Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, + 1, + PRIMARY_USER.id, + ) + + assertThat(whenToDreamState).isEqualTo(WhenToDream.WHILE_DOCKED) + } + + @Test + fun whenToDream_postured() = + kosmos.runTest { + val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER)) + + fakeSettings.putIntForUser( + Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED, + 1, + PRIMARY_USER.id, + ) + + assertThat(whenToDreamState).isEqualTo(WhenToDream.WHILE_POSTURED) + } + + @Test + fun whenToDream_default() = + kosmos.runTest { + val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER)) + assertThat(whenToDreamState).isEqualTo(WhenToDream.NEVER) } private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index 7ae0577bd289..c9e7a5d7df05 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -38,13 +38,9 @@ import com.android.systemui.Flags.FLAG_COMMUNAL_WIDGET_RESIZING import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2 import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.broadcastDispatcher +import com.android.systemui.common.data.repository.batteryRepository +import com.android.systemui.common.data.repository.fake import com.android.systemui.communal.data.model.CommunalSmartspaceTimer -import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository -import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository -import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository -import com.android.systemui.communal.data.repository.FakeCommunalSmartspaceRepository -import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository -import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository import com.android.systemui.communal.data.repository.fakeCommunalPrefsRepository import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository @@ -53,52 +49,49 @@ import com.android.systemui.communal.data.repository.fakeCommunalTutorialReposit import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel +import com.android.systemui.communal.posturing.data.repository.fake +import com.android.systemui.communal.posturing.data.repository.posturingRepository +import com.android.systemui.communal.posturing.shared.model.PosturedState import com.android.systemui.communal.shared.model.CommunalContentSize import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.EditModeState -import com.android.systemui.communal.widgets.EditWidgetsActivityStarter -import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.dock.DockManager +import com.android.systemui.dock.fakeDockManager import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope -import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.plugins.activityStarter -import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes -import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.fakeUserTracker import com.android.systemui.statusbar.phone.fakeManagedProfileController import com.android.systemui.testKosmos -import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.whenever -import com.android.systemui.utils.leaks.FakeManagedProfileController +import com.android.systemui.util.settings.fakeSettings import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.advanceTimeBy -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.eq -import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters @@ -107,32 +100,15 @@ import platform.test.runner.parameterized.Parameters * [CommunalInteractorCommunalDisabledTest]. */ @SmallTest -@OptIn(ExperimentalCoroutinesApi::class) @RunWith(ParameterizedAndroidJunit4::class) class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { - @Mock private lateinit var mainUser: UserInfo - @Mock private lateinit var secondaryUser: UserInfo - - private val kosmos = testKosmos() - private val testScope = kosmos.testScope - - private lateinit var tutorialRepository: FakeCommunalTutorialRepository - private lateinit var communalRepository: FakeCommunalSceneRepository - private lateinit var mediaRepository: FakeCommunalMediaRepository - private lateinit var widgetRepository: FakeCommunalWidgetRepository - private lateinit var smartspaceRepository: FakeCommunalSmartspaceRepository - private lateinit var userRepository: FakeUserRepository - private lateinit var keyguardRepository: FakeKeyguardRepository - private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository - private lateinit var editWidgetsActivityStarter: EditWidgetsActivityStarter - private lateinit var sceneInteractor: SceneInteractor - private lateinit var communalSceneInteractor: CommunalSceneInteractor - private lateinit var userTracker: FakeUserTracker - private lateinit var activityStarter: ActivityStarter - private lateinit var userManager: UserManager - private lateinit var managedProfileController: FakeManagedProfileController - - private lateinit var underTest: CommunalInteractor + private val mainUser = + UserInfo(/* id= */ 0, /* name= */ "primary user", /* flags= */ UserInfo.FLAG_MAIN) + private val secondaryUser = UserInfo(/* id= */ 1, /* name= */ "secondary user", /* flags= */ 0) + + private val kosmos = testKosmos().useUnconfinedTestDispatcher() + + private val Kosmos.underTest by Kosmos.Fixture { communalInteractor } init { mSetFlagsRule.setFlagsParameterization(flags) @@ -140,128 +116,104 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Before fun setUp() { - MockitoAnnotations.initMocks(this) - - tutorialRepository = kosmos.fakeCommunalTutorialRepository - communalRepository = kosmos.fakeCommunalSceneRepository - mediaRepository = kosmos.fakeCommunalMediaRepository - widgetRepository = kosmos.fakeCommunalWidgetRepository - smartspaceRepository = kosmos.fakeCommunalSmartspaceRepository - userRepository = kosmos.fakeUserRepository - keyguardRepository = kosmos.fakeKeyguardRepository - editWidgetsActivityStarter = kosmos.editWidgetsActivityStarter - communalPrefsRepository = kosmos.fakeCommunalPrefsRepository - sceneInteractor = kosmos.sceneInteractor - communalSceneInteractor = kosmos.communalSceneInteractor - userTracker = kosmos.fakeUserTracker - activityStarter = kosmos.activityStarter - userManager = kosmos.userManager - managedProfileController = kosmos.fakeManagedProfileController - - whenever(mainUser.isMain).thenReturn(true) - whenever(secondaryUser.isMain).thenReturn(false) - whenever(userManager.isQuietModeEnabled(any<UserHandle>())).thenReturn(false) - whenever(userManager.isManagedProfile(anyInt())).thenReturn(false) - userRepository.setUserInfos(listOf(mainUser, secondaryUser)) + whenever(kosmos.userManager.isQuietModeEnabled(any<UserHandle>())).thenReturn(false) + whenever(kosmos.userManager.isManagedProfile(anyInt())).thenReturn(false) + kosmos.fakeUserRepository.setUserInfos(listOf(mainUser, secondaryUser)) kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true) mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB) - - underTest = kosmos.communalInteractor } @Test fun communalEnabled_true() = - testScope.runTest { - userRepository.setSelectedUserInfo(mainUser) - runCurrent() + kosmos.runTest { + fakeUserRepository.setSelectedUserInfo(mainUser) assertThat(underTest.isCommunalEnabled.value).isTrue() } @Test fun isCommunalAvailable_storageUnlockedAndMainUser_true() = - testScope.runTest { + kosmos.runTest { val isAvailable by collectLastValue(underTest.isCommunalAvailable) assertThat(isAvailable).isFalse() - keyguardRepository.setIsEncryptedOrLockdown(false) - userRepository.setSelectedUserInfo(mainUser) - keyguardRepository.setKeyguardShowing(true) + fakeKeyguardRepository.setIsEncryptedOrLockdown(false) + fakeUserRepository.setSelectedUserInfo(mainUser) + fakeKeyguardRepository.setKeyguardShowing(true) assertThat(isAvailable).isTrue() } @Test fun isCommunalAvailable_storageLockedAndMainUser_false() = - testScope.runTest { + kosmos.runTest { val isAvailable by collectLastValue(underTest.isCommunalAvailable) assertThat(isAvailable).isFalse() - keyguardRepository.setIsEncryptedOrLockdown(true) - userRepository.setSelectedUserInfo(mainUser) - keyguardRepository.setKeyguardShowing(true) + fakeKeyguardRepository.setIsEncryptedOrLockdown(true) + fakeUserRepository.setSelectedUserInfo(mainUser) + fakeKeyguardRepository.setKeyguardShowing(true) assertThat(isAvailable).isFalse() } @Test fun isCommunalAvailable_storageUnlockedAndSecondaryUser_false() = - testScope.runTest { + kosmos.runTest { val isAvailable by collectLastValue(underTest.isCommunalAvailable) assertThat(isAvailable).isFalse() - keyguardRepository.setIsEncryptedOrLockdown(false) - userRepository.setSelectedUserInfo(secondaryUser) - keyguardRepository.setKeyguardShowing(true) + fakeKeyguardRepository.setIsEncryptedOrLockdown(false) + fakeUserRepository.setSelectedUserInfo(secondaryUser) + fakeKeyguardRepository.setKeyguardShowing(true) assertThat(isAvailable).isFalse() } @Test fun isCommunalAvailable_whenKeyguardShowing_true() = - testScope.runTest { + kosmos.runTest { val isAvailable by collectLastValue(underTest.isCommunalAvailable) assertThat(isAvailable).isFalse() - keyguardRepository.setIsEncryptedOrLockdown(false) - userRepository.setSelectedUserInfo(mainUser) - keyguardRepository.setKeyguardShowing(true) + fakeKeyguardRepository.setIsEncryptedOrLockdown(false) + fakeUserRepository.setSelectedUserInfo(mainUser) + fakeKeyguardRepository.setKeyguardShowing(true) assertThat(isAvailable).isTrue() } @Test fun isCommunalAvailable_communalDisabled_false() = - testScope.runTest { + kosmos.runTest { mSetFlagsRule.disableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2) val isAvailable by collectLastValue(underTest.isCommunalAvailable) assertThat(isAvailable).isFalse() - keyguardRepository.setIsEncryptedOrLockdown(false) - userRepository.setSelectedUserInfo(mainUser) - keyguardRepository.setKeyguardShowing(true) + fakeKeyguardRepository.setIsEncryptedOrLockdown(false) + fakeUserRepository.setSelectedUserInfo(mainUser) + fakeKeyguardRepository.setKeyguardShowing(true) assertThat(isAvailable).isFalse() } @Test fun widget_tutorialCompletedAndWidgetsAvailable_showWidgetContent() = - testScope.runTest { + kosmos.runTest { // Keyguard showing, and tutorial completed. - keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setKeyguardOccluded(false) - tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) + fakeKeyguardRepository.setKeyguardShowing(true) + fakeKeyguardRepository.setKeyguardOccluded(false) + fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK) - userRepository.setUserInfos(userInfos) - userTracker.set(userInfos = userInfos, selectedUserIndex = 0) - runCurrent() + fakeUserRepository.setUserInfos(userInfos) + fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0) // Widgets available. - widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id) - widgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id) - widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id) + fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id) + fakeCommunalWidgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id) + fakeCommunalWidgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id) val widgetContent by collectLastValue(underTest.widgetContent) @@ -356,18 +308,18 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { totalTargets: Int, expectedSizes: List<CommunalContentSize>, ) = - testScope.runTest { + kosmos.runTest { // Keyguard showing, and tutorial completed. - keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setKeyguardOccluded(false) - tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) + fakeKeyguardRepository.setKeyguardShowing(true) + fakeKeyguardRepository.setKeyguardOccluded(false) + fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) val targets = mutableListOf<CommunalSmartspaceTimer>() for (index in 0 until totalTargets) { targets.add(smartspaceTimer(index.toString())) } - smartspaceRepository.setTimers(targets) + fakeCommunalSmartspaceRepository.setTimers(targets) val smartspaceContent by collectLastValue(underTest.ongoingContent(false)) assertThat(smartspaceContent?.size).isEqualTo(totalTargets) @@ -378,12 +330,12 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun umo_mediaPlaying_showsUmo() = - testScope.runTest { + kosmos.runTest { // Tutorial completed. - tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) + fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) // Media is playing. - mediaRepository.mediaActive() + fakeCommunalMediaRepository.mediaActive() val umoContent by collectLastValue(underTest.ongoingContent(true)) @@ -394,12 +346,12 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun umo_mediaPlaying_mediaHostNotVisible_hidesUmo() = - testScope.runTest { + kosmos.runTest { // Tutorial completed. - tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) + fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) // Media is playing. - mediaRepository.mediaActive() + fakeCommunalMediaRepository.mediaActive() val umoContent by collectLastValue(underTest.ongoingContent(false)) assertThat(umoContent?.size).isEqualTo(0) @@ -409,26 +361,26 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID) fun ongoing_shouldOrderAndSizeByTimestamp() = - testScope.runTest { + kosmos.runTest { // Keyguard showing, and tutorial completed. - keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setKeyguardOccluded(false) - tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) + fakeKeyguardRepository.setKeyguardShowing(true) + fakeKeyguardRepository.setKeyguardOccluded(false) + fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) // Timer1 started val timer1 = smartspaceTimer("timer1", timestamp = 1L) - smartspaceRepository.setTimers(listOf(timer1)) + fakeCommunalSmartspaceRepository.setTimers(listOf(timer1)) // Umo started - mediaRepository.mediaActive(timestamp = 2L) + fakeCommunalMediaRepository.mediaActive(timestamp = 2L) // Timer2 started val timer2 = smartspaceTimer("timer2", timestamp = 3L) - smartspaceRepository.setTimers(listOf(timer1, timer2)) + fakeCommunalSmartspaceRepository.setTimers(listOf(timer1, timer2)) // Timer3 started val timer3 = smartspaceTimer("timer3", timestamp = 4L) - smartspaceRepository.setTimers(listOf(timer1, timer2, timer3)) + fakeCommunalSmartspaceRepository.setTimers(listOf(timer1, timer2, timer3)) val ongoingContent by collectLastValue(underTest.ongoingContent(true)) assertThat(ongoingContent?.size).isEqualTo(4) @@ -447,8 +399,8 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun ctaTile_showsByDefault() = - testScope.runTest { - tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) + kosmos.runTest { + fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) val ctaTileContent by collectLastValue(underTest.ctaTileContent) @@ -461,14 +413,13 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun ctaTile_afterDismiss_doesNotShow() = - testScope.runTest { + kosmos.runTest { // Set to main user, so we can dismiss the tile for the main user. - val user = userRepository.asMainUser() - userTracker.set(userInfos = listOf(user), selectedUserIndex = 0) - runCurrent() + val user = fakeUserRepository.asMainUser() + fakeUserTracker.set(userInfos = listOf(user), selectedUserIndex = 0) - tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) - communalPrefsRepository.setCtaDismissed(user) + fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) + fakeCommunalPrefsRepository.setCtaDismissed(user) val ctaTileContent by collectLastValue(underTest.ctaTileContent) @@ -477,36 +428,30 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun listensToSceneChange() = - testScope.runTest { + kosmos.runTest { kosmos.setCommunalAvailable(true) - runCurrent() - var desiredScene = collectLastValue(underTest.desiredScene) - runCurrent() - assertThat(desiredScene()).isEqualTo(CommunalScenes.Blank) + val desiredScene by collectLastValue(underTest.desiredScene) + assertThat(desiredScene).isEqualTo(CommunalScenes.Blank) val targetScene = CommunalScenes.Communal - communalRepository.changeScene(targetScene) - desiredScene = collectLastValue(underTest.desiredScene) - runCurrent() - assertThat(desiredScene()).isEqualTo(targetScene) + fakeCommunalSceneRepository.changeScene(targetScene) + assertThat(desiredScene).isEqualTo(targetScene) } @Test fun updatesScene() = - testScope.runTest { + kosmos.runTest { val targetScene = CommunalScenes.Communal - underTest.changeScene(targetScene, "test") - val desiredScene = collectLastValue(communalRepository.currentScene) - runCurrent() - assertThat(desiredScene()).isEqualTo(targetScene) + val desiredScene by collectLastValue(fakeCommunalSceneRepository.currentScene) + assertThat(desiredScene).isEqualTo(targetScene) } @Test fun transitionProgress_onTargetScene_fullProgress() = - testScope.runTest { + kosmos.runTest { val targetScene = CommunalScenes.Blank val transitionProgressFlow = underTest.transitionProgressToScene(targetScene) val transitionProgress by collectLastValue(transitionProgressFlow) @@ -524,7 +469,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun transitionProgress_notOnTargetScene_noProgress() = - testScope.runTest { + kosmos.runTest { val targetScene = CommunalScenes.Blank val currentScene = CommunalScenes.Communal val transitionProgressFlow = underTest.transitionProgressToScene(targetScene) @@ -543,7 +488,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun transitionProgress_transitioningToTrackedScene() = - testScope.runTest { + kosmos.runTest { val currentScene = CommunalScenes.Communal val targetScene = CommunalScenes.Blank val transitionProgressFlow = underTest.transitionProgressToScene(targetScene) @@ -591,7 +536,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun transitionProgress_transitioningAwayFromTrackedScene() = - testScope.runTest { + kosmos.runTest { val currentScene = CommunalScenes.Blank val targetScene = CommunalScenes.Communal val transitionProgressFlow = underTest.transitionProgressToScene(currentScene) @@ -642,52 +587,42 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun isCommunalShowing() = - testScope.runTest { + kosmos.runTest { kosmos.setCommunalAvailable(true) - runCurrent() - var isCommunalShowing = collectLastValue(underTest.isCommunalShowing) - runCurrent() - assertThat(isCommunalShowing()).isEqualTo(false) + val isCommunalShowing by collectLastValue(underTest.isCommunalShowing) + assertThat(isCommunalShowing).isEqualTo(false) underTest.changeScene(CommunalScenes.Communal, "test") - - isCommunalShowing = collectLastValue(underTest.isCommunalShowing) - runCurrent() - assertThat(isCommunalShowing()).isEqualTo(true) + assertThat(isCommunalShowing).isEqualTo(true) } @Test fun isCommunalShowing_whenSceneContainerDisabled() = - testScope.runTest { + kosmos.runTest { kosmos.setCommunalAvailable(true) - runCurrent() // Verify default is false val isCommunalShowing by collectLastValue(underTest.isCommunalShowing) - runCurrent() assertThat(isCommunalShowing).isFalse() // Verify scene changes with the flag doesn't have any impact sceneInteractor.changeScene(Scenes.Communal, loggingReason = "") - runCurrent() assertThat(isCommunalShowing).isFalse() // Verify scene changes (without the flag) to communal sets the value to true underTest.changeScene(CommunalScenes.Communal, "test") - runCurrent() assertThat(isCommunalShowing).isTrue() // Verify scene changes (without the flag) to blank sets the value back to false underTest.changeScene(CommunalScenes.Blank, "test") - runCurrent() assertThat(isCommunalShowing).isFalse() } @Test @EnableSceneContainer fun isCommunalShowing_whenSceneContainerEnabled() = - testScope.runTest { + kosmos.runTest { // Verify default is false val isCommunalShowing by collectLastValue(underTest.isCommunalShowing) assertThat(isCommunalShowing).isFalse() @@ -704,7 +639,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test @EnableSceneContainer fun isCommunalShowing_whenSceneContainerEnabledAndChangeToLegacyScene() = - testScope.runTest { + kosmos.runTest { // Verify default is false val isCommunalShowing by collectLastValue(underTest.isCommunalShowing) assertThat(isCommunalShowing).isFalse() @@ -720,21 +655,19 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun isIdleOnCommunal() = - testScope.runTest { + kosmos.runTest { val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Idle(CommunalScenes.Blank) ) - communalRepository.setTransitionState(transitionState) + fakeCommunalSceneRepository.setTransitionState(transitionState) // isIdleOnCommunal is false when not on communal. val isIdleOnCommunal by collectLastValue(underTest.isIdleOnCommunal) - runCurrent() assertThat(isIdleOnCommunal).isEqualTo(false) // Transition to communal. transitionState.value = ObservableTransitionState.Idle(CommunalScenes.Communal) - runCurrent() // isIdleOnCommunal is now true since we're on communal. assertThat(isIdleOnCommunal).isEqualTo(true) @@ -749,7 +682,6 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), ) - runCurrent() // isIdleOnCommunal turns false as soon as transition away starts. assertThat(isIdleOnCommunal).isEqualTo(false) @@ -757,12 +689,12 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun isCommunalVisible() = - testScope.runTest { + kosmos.runTest { val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Idle(CommunalScenes.Blank) ) - communalRepository.setTransitionState(transitionState) + fakeCommunalSceneRepository.setTransitionState(transitionState) // isCommunalVisible is false when not on communal. val isCommunalVisible by collectLastValue(underTest.isCommunalVisible) @@ -805,7 +737,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun testShowWidgetEditorStartsActivity() = - testScope.runTest { + kosmos.runTest { val editModeState by collectLastValue(communalSceneInteractor.editModeState) underTest.showWidgetEditor() @@ -816,14 +748,14 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun showWidgetEditor_openWidgetPickerOnStart_startsActivity() = - testScope.runTest { + kosmos.runTest { underTest.showWidgetEditor(shouldOpenWidgetPickerOnStart = true) verify(editWidgetsActivityStarter).startActivity(shouldOpenWidgetPickerOnStart = true) } @Test fun navigateToCommunalWidgetSettings_startsActivity() = - testScope.runTest { + kosmos.runTest { underTest.navigateToCommunalWidgetSettings() val intentCaptor = argumentCaptor<Intent>() verify(activityStarter) @@ -833,23 +765,22 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun filterWidgets_whenUserProfileRemoved() = - testScope.runTest { + kosmos.runTest { // Keyguard showing, and tutorial completed. - keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setKeyguardOccluded(false) - tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) + fakeKeyguardRepository.setKeyguardShowing(true) + fakeKeyguardRepository.setKeyguardOccluded(false) + fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) // Only main user exists. val userInfos = listOf(MAIN_USER_INFO) - userRepository.setUserInfos(userInfos) - userTracker.set(userInfos = userInfos, selectedUserIndex = 0) - runCurrent() + fakeUserRepository.setUserInfos(userInfos) + fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0) val widgetContent by collectLastValue(underTest.widgetContent) // Given three widgets, and one of them is associated with pre-existing work profile. - widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id) - widgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id) - widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id) + fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id) + fakeCommunalWidgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id) + fakeCommunalWidgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id) // One widget is filtered out and the remaining two link to main user id. assertThat(checkNotNull(widgetContent).size).isEqualTo(2) @@ -867,17 +798,16 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun widgetContent_inQuietMode() = - testScope.runTest { + kosmos.runTest { // Keyguard showing, and tutorial completed. - keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setKeyguardOccluded(false) - tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) + fakeKeyguardRepository.setKeyguardShowing(true) + fakeKeyguardRepository.setKeyguardOccluded(false) + fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) // Work profile is set up. val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK) - userRepository.setUserInfos(userInfos) - userTracker.set(userInfos = userInfos, selectedUserIndex = 0) - runCurrent() + fakeUserRepository.setUserInfos(userInfos) + fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0) // When work profile is paused. whenever(userManager.isQuietModeEnabled(eq(UserHandle.of(USER_INFO_WORK.id)))) @@ -885,9 +815,9 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { whenever(userManager.isManagedProfile(eq(USER_INFO_WORK.id))).thenReturn(true) val widgetContent by collectLastValue(underTest.widgetContent) - widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id) - widgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id) - widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id) + fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id) + fakeCommunalWidgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id) + fakeCommunalWidgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id) // The work profile widget is in quiet mode, while other widgets are not. assertThat(widgetContent).hasSize(3) @@ -911,23 +841,25 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun filterWidgets_whenDisallowedByDevicePolicyForWorkProfile() = - testScope.runTest { + kosmos.runTest { // Keyguard showing, and tutorial completed. - keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setKeyguardOccluded(false) - tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) + fakeKeyguardRepository.setKeyguardShowing(true) + fakeKeyguardRepository.setKeyguardOccluded(false) + fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK) - userRepository.setUserInfos(userInfos) - userTracker.set(userInfos = userInfos, selectedUserIndex = 0) - userRepository.setSelectedUserInfo(MAIN_USER_INFO) - runCurrent() + fakeUserRepository.setUserInfos(userInfos) + fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0) + fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO) val widgetContent by collectLastValue(underTest.widgetContent) // One available work widget, one pending work widget, and one regular available widget. - widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id) - widgetRepository.addPendingWidget(appWidgetId = 2, userId = USER_INFO_WORK.id) - widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id) + fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id) + fakeCommunalWidgetRepository.addPendingWidget( + appWidgetId = 2, + userId = USER_INFO_WORK.id, + ) + fakeCommunalWidgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id) setKeyguardFeaturesDisabled( USER_INFO_WORK, @@ -941,23 +873,25 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun filterWidgets_whenAllowedByDevicePolicyForWorkProfile() = - testScope.runTest { + kosmos.runTest { // Keyguard showing, and tutorial completed. - keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setKeyguardOccluded(false) - tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) + fakeKeyguardRepository.setKeyguardShowing(true) + fakeKeyguardRepository.setKeyguardOccluded(false) + fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK) - userRepository.setUserInfos(userInfos) - userTracker.set(userInfos = userInfos, selectedUserIndex = 0) - userRepository.setSelectedUserInfo(MAIN_USER_INFO) - runCurrent() + fakeUserRepository.setUserInfos(userInfos) + fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0) + fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO) val widgetContent by collectLastValue(underTest.widgetContent) // Given three widgets, and one of them is associated with work profile. - widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id) - widgetRepository.addPendingWidget(appWidgetId = 2, userId = USER_INFO_WORK.id) - widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id) + fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id) + fakeCommunalWidgetRepository.addPendingWidget( + appWidgetId = 2, + userId = USER_INFO_WORK.id, + ) + fakeCommunalWidgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id) setKeyguardFeaturesDisabled( USER_INFO_WORK, @@ -973,7 +907,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun showCommunalFromOccluded_enteredOccludedFromHub() = - testScope.runTest { + kosmos.runTest { kosmos.setCommunalAvailable(true) val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded) assertThat(showCommunalFromOccluded).isFalse() @@ -989,7 +923,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun showCommunalFromOccluded_enteredOccludedFromLockscreen() = - testScope.runTest { + kosmos.runTest { kosmos.setCommunalAvailable(true) val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded) assertThat(showCommunalFromOccluded).isFalse() @@ -1005,7 +939,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun showCommunalFromOccluded_communalBecomesUnavailableWhileOccluded() = - testScope.runTest { + kosmos.runTest { kosmos.setCommunalAvailable(true) val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded) assertThat(showCommunalFromOccluded).isFalse() @@ -1015,7 +949,6 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { to = KeyguardState.OCCLUDED, testScope, ) - runCurrent() kosmos.setCommunalAvailable(false) assertThat(showCommunalFromOccluded).isFalse() @@ -1023,7 +956,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun showCommunalFromOccluded_showBouncerWhileOccluded() = - testScope.runTest { + kosmos.runTest { kosmos.setCommunalAvailable(true) val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded) assertThat(showCommunalFromOccluded).isFalse() @@ -1033,7 +966,6 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { to = KeyguardState.OCCLUDED, testScope, ) - runCurrent() kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.OCCLUDED, to = KeyguardState.PRIMARY_BOUNCER, @@ -1045,7 +977,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun showCommunalFromOccluded_enteredOccludedFromDreaming() = - testScope.runTest { + kosmos.runTest { kosmos.setCommunalAvailable(true) val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded) assertThat(showCommunalFromOccluded).isFalse() @@ -1069,7 +1001,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun dismissDisclaimerSetsDismissedFlag() = - testScope.runTest { + kosmos.runTest { val disclaimerDismissed by collectLastValue(underTest.isDisclaimerDismissed) assertThat(disclaimerDismissed).isFalse() underTest.setDisclaimerDismissed() @@ -1078,17 +1010,17 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun dismissDisclaimerTimeoutResetsDismissedFlag() = - testScope.runTest { + kosmos.runTest { val disclaimerDismissed by collectLastValue(underTest.isDisclaimerDismissed) underTest.setDisclaimerDismissed() assertThat(disclaimerDismissed).isTrue() - advanceTimeBy(CommunalInteractor.DISCLAIMER_RESET_MILLIS) + testScope.advanceTimeBy(CommunalInteractor.DISCLAIMER_RESET_MILLIS) assertThat(disclaimerDismissed).isFalse() } @Test fun settingSelectedKey_flowUpdated() { - testScope.runTest { + kosmos.runTest { val key = "test" val selectedKey by collectLastValue(underTest.selectedKey) underTest.setSelectedKey(key) @@ -1098,36 +1030,35 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun unpauseWorkProfileEnablesWorkMode() = - testScope.runTest { + kosmos.runTest { underTest.unpauseWorkProfile() - assertThat(managedProfileController.isWorkModeEnabled()).isTrue() + assertThat(fakeManagedProfileController.isWorkModeEnabled()).isTrue() } @Test @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING) @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID) fun resizeWidget_withoutUpdatingOrder() = - testScope.runTest { + kosmos.runTest { val userInfos = listOf(MAIN_USER_INFO) - userRepository.setUserInfos(userInfos) - userTracker.set(userInfos = userInfos, selectedUserIndex = 0) - runCurrent() + fakeUserRepository.setUserInfos(userInfos) + fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0) // Widgets available. - widgetRepository.addWidget( + fakeCommunalWidgetRepository.addWidget( appWidgetId = 1, userId = MAIN_USER_INFO.id, rank = 0, spanY = CommunalContentSize.FixedSize.HALF.span, ) - widgetRepository.addWidget( + fakeCommunalWidgetRepository.addWidget( appWidgetId = 2, userId = MAIN_USER_INFO.id, rank = 1, spanY = CommunalContentSize.FixedSize.HALF.span, ) - widgetRepository.addWidget( + fakeCommunalWidgetRepository.addWidget( appWidgetId = 3, userId = MAIN_USER_INFO.id, rank = 2, @@ -1159,26 +1090,25 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING, FLAG_COMMUNAL_RESPONSIVE_GRID) fun resizeWidget_withoutUpdatingOrder_responsive() = - testScope.runTest { + kosmos.runTest { val userInfos = listOf(MAIN_USER_INFO) - userRepository.setUserInfos(userInfos) - userTracker.set(userInfos = userInfos, selectedUserIndex = 0) - runCurrent() + fakeUserRepository.setUserInfos(userInfos) + fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0) // Widgets available. - widgetRepository.addWidget( + fakeCommunalWidgetRepository.addWidget( appWidgetId = 1, userId = MAIN_USER_INFO.id, rank = 0, spanY = 1, ) - widgetRepository.addWidget( + fakeCommunalWidgetRepository.addWidget( appWidgetId = 2, userId = MAIN_USER_INFO.id, rank = 1, spanY = 1, ) - widgetRepository.addWidget( + fakeCommunalWidgetRepository.addWidget( appWidgetId = 3, userId = MAIN_USER_INFO.id, rank = 2, @@ -1211,26 +1141,25 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING) @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID) fun resizeWidget_andUpdateOrder() = - testScope.runTest { + kosmos.runTest { val userInfos = listOf(MAIN_USER_INFO) - userRepository.setUserInfos(userInfos) - userTracker.set(userInfos = userInfos, selectedUserIndex = 0) - runCurrent() + fakeUserRepository.setUserInfos(userInfos) + fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0) // Widgets available. - widgetRepository.addWidget( + fakeCommunalWidgetRepository.addWidget( appWidgetId = 1, userId = MAIN_USER_INFO.id, rank = 0, spanY = CommunalContentSize.FixedSize.HALF.span, ) - widgetRepository.addWidget( + fakeCommunalWidgetRepository.addWidget( appWidgetId = 2, userId = MAIN_USER_INFO.id, rank = 1, spanY = CommunalContentSize.FixedSize.HALF.span, ) - widgetRepository.addWidget( + fakeCommunalWidgetRepository.addWidget( appWidgetId = 3, userId = MAIN_USER_INFO.id, rank = 2, @@ -1266,26 +1195,25 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING, FLAG_COMMUNAL_RESPONSIVE_GRID) fun resizeWidget_andUpdateOrder_responsive() = - testScope.runTest { + kosmos.runTest { val userInfos = listOf(MAIN_USER_INFO) - userRepository.setUserInfos(userInfos) - userTracker.set(userInfos = userInfos, selectedUserIndex = 0) - runCurrent() + fakeUserRepository.setUserInfos(userInfos) + fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0) // Widgets available. - widgetRepository.addWidget( + fakeCommunalWidgetRepository.addWidget( appWidgetId = 1, userId = MAIN_USER_INFO.id, rank = 0, spanY = 1, ) - widgetRepository.addWidget( + fakeCommunalWidgetRepository.addWidget( appWidgetId = 2, userId = MAIN_USER_INFO.id, rank = 1, spanY = 1, ) - widgetRepository.addWidget( + fakeCommunalWidgetRepository.addWidget( appWidgetId = 3, userId = MAIN_USER_INFO.id, rank = 2, @@ -1318,6 +1246,66 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { .inOrder() } + @Test + fun showCommunalWhileCharging() = + kosmos.runTest { + fakeKeyguardRepository.setIsEncryptedOrLockdown(false) + fakeUserRepository.setSelectedUserInfo(mainUser) + fakeKeyguardRepository.setKeyguardShowing(true) + fakeSettings.putIntForUser( + Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, + 1, + mainUser.id, + ) + + val shouldShowCommunal by collectLastValue(underTest.shouldShowCommunal) + batteryRepository.fake.setDevicePluggedIn(false) + assertThat(shouldShowCommunal).isFalse() + + batteryRepository.fake.setDevicePluggedIn(true) + assertThat(shouldShowCommunal).isTrue() + } + + @Test + fun showCommunalWhilePosturedAndCharging() = + kosmos.runTest { + fakeKeyguardRepository.setIsEncryptedOrLockdown(false) + fakeUserRepository.setSelectedUserInfo(mainUser) + fakeKeyguardRepository.setKeyguardShowing(true) + fakeSettings.putIntForUser( + Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED, + 1, + mainUser.id, + ) + + val shouldShowCommunal by collectLastValue(underTest.shouldShowCommunal) + batteryRepository.fake.setDevicePluggedIn(true) + posturingRepository.fake.setPosturedState(PosturedState.NotPostured) + assertThat(shouldShowCommunal).isFalse() + + posturingRepository.fake.setPosturedState(PosturedState.Postured(1f)) + assertThat(shouldShowCommunal).isTrue() + } + + @Test + fun showCommunalWhileDocked() = + kosmos.runTest { + fakeKeyguardRepository.setIsEncryptedOrLockdown(false) + fakeUserRepository.setSelectedUserInfo(mainUser) + fakeKeyguardRepository.setKeyguardShowing(true) + fakeSettings.putIntForUser(Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, 1, mainUser.id) + + batteryRepository.fake.setDevicePluggedIn(true) + fakeDockManager.setIsDocked(false) + + val shouldShowCommunal by collectLastValue(underTest.shouldShowCommunal) + assertThat(shouldShowCommunal).isFalse() + + fakeDockManager.setIsDocked(true) + fakeDockManager.setDockEvent(DockManager.STATE_DOCKED) + assertThat(shouldShowCommunal).isTrue() + } + private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) { whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id))) .thenReturn(disabledFlags) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorTest.kt index 1fef6932ecca..1f5f8cedab02 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractorTest.kt @@ -108,6 +108,43 @@ class CommunalPrefsInteractorTest : SysuiTestCase() { assertThat(isHubOnboardingDismissed).isFalse() } + @Test + fun setDreamButtonTooltipDismissed_currentUser() = + testScope.runTest { + setSelectedUser(MAIN_USER) + val isDreamButtonTooltipDismissed by + collectLastValue(underTest.isDreamButtonTooltipDismissed) + + assertThat(isDreamButtonTooltipDismissed).isFalse() + underTest.setDreamButtonTooltipDismissed(MAIN_USER) + assertThat(isDreamButtonTooltipDismissed).isTrue() + } + + @Test + fun setDreamButtonTooltipDismissed_anotherUser() = + testScope.runTest { + setSelectedUser(MAIN_USER) + val isDreamButtonTooltipDismissed by + collectLastValue(underTest.isDreamButtonTooltipDismissed) + + assertThat(isDreamButtonTooltipDismissed).isFalse() + underTest.setDreamButtonTooltipDismissed(SECONDARY_USER) + assertThat(isDreamButtonTooltipDismissed).isFalse() + } + + @Test + fun isDreamButtonTooltipDismissed_userSwitch() = + testScope.runTest { + setSelectedUser(MAIN_USER) + underTest.setDreamButtonTooltipDismissed(MAIN_USER) + val isDreamButtonTooltipDismissed by + collectLastValue(underTest.isDreamButtonTooltipDismissed) + + assertThat(isDreamButtonTooltipDismissed).isTrue() + setSelectedUser(SECONDARY_USER) + assertThat(isDreamButtonTooltipDismissed).isFalse() + } + private suspend fun setSelectedUser(user: UserInfo) { with(kosmos.fakeUserRepository) { setUserInfos(listOf(user)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorTest.kt index e4916b1a7e46..310bf6486413 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorTest.kt @@ -21,19 +21,17 @@ import android.app.admin.devicePolicyManager import android.content.Intent import android.content.pm.UserInfo import android.os.UserManager -import android.os.userManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.broadcastDispatcher -import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.testScope -import com.android.systemui.settings.FakeUserTracker +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.settings.fakeUserTracker import com.android.systemui.testKosmos -import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.data.repository.fakeUserRepository -import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull @@ -48,34 +46,20 @@ import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class CommunalSettingsInteractorTest : SysuiTestCase() { - private lateinit var userManager: UserManager - private lateinit var userRepository: FakeUserRepository - private lateinit var userTracker: FakeUserTracker + private val kosmos = testKosmos().useUnconfinedTestDispatcher() - private val kosmos = testKosmos() - private val testScope = kosmos.testScope - - private lateinit var underTest: CommunalSettingsInteractor + private val Kosmos.underTest by Kosmos.Fixture { communalSettingsInteractor } @Before fun setUp() { - userManager = kosmos.userManager - userRepository = kosmos.fakeUserRepository - userTracker = kosmos.fakeUserTracker - val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK) - userRepository.setUserInfos(userInfos) - userTracker.set( - userInfos = userInfos, - selectedUserIndex = 0, - ) - - underTest = kosmos.communalSettingsInteractor + kosmos.fakeUserRepository.setUserInfos(userInfos) + kosmos.fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0) } @Test fun filterUsers_dontFilteredUsersWhenAllAreAllowed() = - testScope.runTest { + kosmos.runTest { // If no users have any keyguard features disabled... val disallowedUser by collectLastValue(underTest.workProfileUserDisallowedByDevicePolicy) @@ -85,11 +69,11 @@ class CommunalSettingsInteractorTest : SysuiTestCase() { @Test fun filterUsers_filterWorkProfileUserWhenDisallowed() = - testScope.runTest { + kosmos.runTest { // If the work profile user has keyguard widgets disabled... setKeyguardFeaturesDisabled( USER_INFO_WORK, - DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL + DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL, ) // ...then the disallowed user match the work profile val disallowedUser by @@ -102,7 +86,7 @@ class CommunalSettingsInteractorTest : SysuiTestCase() { whenever( kosmos.devicePolicyManager.getKeyguardDisabledFeatures( anyOrNull(), - ArgumentMatchers.eq(user.id) + ArgumentMatchers.eq(user.id), ) ) .thenReturn(disabledFlags) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt index 012ae8f12d4a..b747705fa3a2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.communal.ui.viewmodel +import android.content.pm.UserInfo import android.platform.test.annotations.EnableFlags import android.provider.Settings import android.service.dream.dreamManager @@ -24,15 +25,17 @@ import androidx.test.filters.SmallTest import com.android.internal.logging.uiEventLoggerFake import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2 import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.data.repository.fakeCommunalPrefsRepository +import com.android.systemui.communal.domain.interactor.HubOnboardingInteractorTest.Companion.MAIN_USER import com.android.systemui.communal.shared.log.CommunalUiEvent import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED import com.android.systemui.flags.fakeFeatureFlagsClassic -import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn import com.android.systemui.plugins.activityStarter +import com.android.systemui.settings.fakeUserTracker import com.android.systemui.statusbar.policy.batteryController import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.fakeUserRepository @@ -52,7 +55,6 @@ import org.mockito.kotlin.whenever class CommunalToDreamButtonViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val uiEventLoggerFake = kosmos.uiEventLoggerFake private val underTest: CommunalToDreamButtonViewModel by lazy { kosmos.communalToDreamButtonViewModel } @@ -68,9 +70,9 @@ class CommunalToDreamButtonViewModelTest : SysuiTestCase() { with(kosmos) { runTest { whenever(batteryController.isPluggedIn()).thenReturn(true) + runCurrent() - val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub) - assertThat(shouldShowButton).isTrue() + assertThat(underTest.shouldShowDreamButtonOnHub).isTrue() } } @@ -79,9 +81,9 @@ class CommunalToDreamButtonViewModelTest : SysuiTestCase() { with(kosmos) { runTest { whenever(batteryController.isPluggedIn()).thenReturn(false) + runCurrent() - val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub) - assertThat(shouldShowButton).isFalse() + assertThat(underTest.shouldShowDreamButtonOnHub).isFalse() } } @@ -124,6 +126,23 @@ class CommunalToDreamButtonViewModelTest : SysuiTestCase() { } @Test + fun shouldShowDreamButtonTooltip_trueWhenNotDismissed() = + kosmos.runTest { + runCurrent() + assertThat(underTest.shouldShowTooltip).isTrue() + } + + @Test + fun shouldShowDreamButtonTooltip_falseWhenDismissed() = + kosmos.runTest { + setSelectedUser(MAIN_USER) + fakeCommunalPrefsRepository.setDreamButtonTooltipDismissed(MAIN_USER) + runCurrent() + + assertThat(underTest.shouldShowTooltip).isFalse() + } + + @Test fun onShowDreamButtonTap_eventLogged() = with(kosmos) { runTest { @@ -134,4 +153,12 @@ class CommunalToDreamButtonViewModelTest : SysuiTestCase() { .isEqualTo(CommunalUiEvent.COMMUNAL_HUB_SHOW_DREAM_BUTTON_TAP.id) } } + + private suspend fun setSelectedUser(user: UserInfo) { + with(kosmos.fakeUserRepository) { + setUserInfos(listOf(user)) + setSelectedUserInfo(user) + } + kosmos.fakeUserTracker.set(userInfos = listOf(user), selectedUserIndex = 0) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt index 6c955bf1818d..5fd480f90ac9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt @@ -176,14 +176,14 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { } @Test - fun nonPowerButtonFPS_coExFaceFailure_doNotVibrateError() = + fun nonPowerButtonFPS_coExFaceFailure_vibrateError() = testScope.runTest { val playErrorHaptic by collectLastValue(underTest.playErrorHaptic) enrollFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC) enrollFace() runCurrent() faceFailure() - assertThat(playErrorHaptic).isNull() + assertThat(playErrorHaptic).isNotNull() } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index ef70305a3f47..af30e435da73 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -1149,7 +1149,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) - fun skipsFaceErrorHaptics_nonSfps_coEx() = + fun playsFaceErrorHaptics_nonSfps_coEx() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic) @@ -1161,14 +1161,15 @@ class SceneContainerStartableTest : SysuiTestCase() { underTest.start() updateFaceAuthStatus(isSuccess = false) - assertThat(playErrorHaptic).isNull() - verify(vibratorHelper, never()).vibrateAuthError(anyString()) + assertThat(playErrorHaptic).isNotNull() + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + verify(vibratorHelper).vibrateAuthError(anyString()) verify(vibratorHelper, never()).vibrateAuthSuccess(anyString()) } @Test @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) - fun skipsMSDLFaceErrorHaptics_nonSfps_coEx() = + fun playsMSDLFaceErrorHaptics_nonSfps_coEx() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic) @@ -1180,9 +1181,10 @@ class SceneContainerStartableTest : SysuiTestCase() { underTest.start() updateFaceAuthStatus(isSuccess = false) - assertThat(playErrorHaptic).isNull() - assertThat(msdlPlayer.latestTokenPlayed).isNull() - assertThat(msdlPlayer.latestPropertiesPlayed).isNull() + assertThat(playErrorHaptic).isNotNull() + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.FAILURE) + assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt index 39c42f183481..28b2ee8dde06 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt @@ -269,6 +269,36 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( } @Test + fun testOpenAndCloseGutsWithoutSave() { + val guts = spy(NotificationGuts(mContext)) + whenever(guts.post(any())).thenAnswer { invocation: InvocationOnMock -> + handler.post(((invocation.arguments[0] as Runnable))) + null + } + + // Test doesn't support animation since the guts view is not attached. + doNothing().whenever(guts).openControls(anyInt(), anyInt(), anyBoolean(), any()) + + val realRow = createTestNotificationRow() + val menuItem = createTestMenuItem(realRow) + + val row = spy(realRow) + whenever(row.windowToken).thenReturn(Binder()) + whenever(row.guts).thenReturn(guts) + + assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem)) + executor.runAllReady() + verify(guts).openControls(anyInt(), anyInt(), anyBoolean(), any<Runnable>()) + + gutsManager.closeAndUndoGuts() + + verify(guts).closeControls(anyInt(), anyInt(), eq(false), eq(false)) + verify(row, times(1)).setGutsView(any<MenuItem>()) + executor.runAllReady() + verify(headsUpManager).setGutsShown(realRow.entry, false) + } + + @Test fun testLockscreenShadeVisible_visible_gutsNotClosed() = testScope.runTest { // First, start out lockscreen or shade as not visible @@ -377,52 +407,6 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( } @Test - fun testChangeDensityOrFontScale() { - val guts = spy(NotificationGuts(mContext)) - whenever(guts.post(any())).thenAnswer { invocation: InvocationOnMock -> - handler.post(((invocation.arguments[0] as Runnable))) - null - } - - // Test doesn't support animation since the guts view is not attached. - doNothing().whenever(guts).openControls(anyInt(), anyInt(), anyBoolean(), any<Runnable>()) - - val realRow = createTestNotificationRow() - val menuItem = createTestMenuItem(realRow) - - val row = spy(realRow) - - whenever(row.windowToken).thenReturn(Binder()) - whenever(row.guts).thenReturn(guts) - doNothing().whenever(row).ensureGutsInflated() - - val realEntry = realRow.entry - val entry = spy(realEntry) - - whenever(entry.row).thenReturn(row) - whenever(entry.guts).thenReturn(guts) - - assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem)) - executor.runAllReady() - verify(guts).openControls(anyInt(), anyInt(), anyBoolean(), any<Runnable>()) - - // called once by mGutsManager.bindGuts() in mGutsManager.openGuts() - verify(row).setGutsView(any<MenuItem>()) - - row.onDensityOrFontScaleChanged() - gutsManager.onDensityOrFontScaleChanged(entry) - - executor.runAllReady() - - gutsManager.closeAndSaveGuts(false, false, false, 0, 0, false) - - verify(guts).closeControls(anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean()) - - // called again by mGutsManager.bindGuts(), in mGutsManager.onDensityOrFontScaleChanged() - verify(row, times(2)).setGutsView(any<MenuItem>()) - } - - @Test fun testAppOpsSettingsIntent_camera() { val row = createTestNotificationRow() val ops = ArraySet<Int>() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java index 6435e8203d3d..af67a04d2f2a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java @@ -16,29 +16,44 @@ package com.android.systemui.statusbar.notification.row; +import static com.google.common.truth.Truth.assertThat; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import android.platform.test.annotations.EnableFlags; import android.provider.Settings; import android.testing.TestableResources; -import android.util.KeyValueListParser; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; import androidx.test.annotation.UiThreadTest; 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.animation.AnimatorTestRule; +import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption; import com.android.systemui.res.R; +import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; +import java.util.List; @SmallTest @RunWith(AndroidJUnit4.class) @@ -46,8 +61,12 @@ import java.util.ArrayList; public class NotificationSnoozeTest extends SysuiTestCase { private static final int RES_DEFAULT = 2; private static final int[] RES_OPTIONS = {1, 2, 3}; - private NotificationSnooze mNotificationSnooze; - private KeyValueListParser mMockParser; + private final NotificationSwipeActionHelper mSnoozeListener = mock( + NotificationSwipeActionHelper.class); + private NotificationSnooze mUnderTest; + + @Rule + public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this); @Before public void setUp() throws Exception { @@ -56,62 +75,117 @@ public class NotificationSnoozeTest extends SysuiTestCase { TestableResources resources = mContext.getOrCreateTestableResources(); resources.addOverride(R.integer.config_notification_snooze_time_default, RES_DEFAULT); resources.addOverride(R.array.config_notification_snooze_times, RES_OPTIONS); - mNotificationSnooze = new NotificationSnooze(mContext, null); - mMockParser = mock(KeyValueListParser.class); + + mUnderTest = new NotificationSnooze(mContext, null); + mUnderTest.setSnoozeListener(mSnoozeListener); + mUnderTest.mExpandButton = mock(ImageView.class); + mUnderTest.mSnoozeView = mock(View.class); + mUnderTest.mSelectedOptionText = mock(TextView.class); + mUnderTest.mDivider = mock(View.class); + mUnderTest.mSnoozeOptionContainer = mock(ViewGroup.class); + mUnderTest.mSnoozeOptions = mock(List.class); + } + + @After + public void tearDown() { + // Make sure all animations are finished + mAnimatorTestRule.advanceTimeBy(1000L); + } + + @Test + @EnableFlags(Flags.FLAG_NOTIFICATION_UNDO_GUTS_ON_CONFIG_CHANGED) + public void closeControls_withoutSave_performsUndo() { + ArrayList<SnoozeOption> options = mUnderTest.getDefaultSnoozeOptions(); + mUnderTest.mSelectedOption = options.getFirst(); + mUnderTest.showSnoozeOptions(true); + + assertThat( + mUnderTest.handleCloseControls(/* save = */ false, /* force = */ false)).isFalse(); + + assertThat(mUnderTest.mSelectedOption).isNull(); + assertThat(mUnderTest.isExpanded()).isFalse(); + verify(mSnoozeListener, times(0)).snooze(any(), any()); + } + + @Test + public void closeControls_whenExpanded_collapsesOptions() { + ArrayList<SnoozeOption> options = mUnderTest.getDefaultSnoozeOptions(); + mUnderTest.mSelectedOption = options.getFirst(); + mUnderTest.showSnoozeOptions(true); + + assertThat(mUnderTest.handleCloseControls(/* save = */ true, /* force = */ false)).isTrue(); + + assertThat(mUnderTest.mSelectedOption).isNotNull(); + assertThat(mUnderTest.isExpanded()).isFalse(); + } + + @Test + public void closeControls_whenCollapsed_commitsChanges() { + ArrayList<SnoozeOption> options = mUnderTest.getDefaultSnoozeOptions(); + mUnderTest.mSelectedOption = options.getFirst(); + + assertThat(mUnderTest.handleCloseControls(/* save = */ true, /* force = */ false)).isTrue(); + + verify(mSnoozeListener).snooze(any(), any()); + } + + @Test + public void closeControls_withForce_returnsFalse() { + assertThat(mUnderTest.handleCloseControls(/* save = */ true, /* force = */ true)).isFalse(); } @Test - public void testGetOptionsWithNoConfig() throws Exception { - ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions(); + public void testGetOptionsWithNoConfig() { + ArrayList<SnoozeOption> result = mUnderTest.getDefaultSnoozeOptions(); assertEquals(3, result.size()); assertEquals(1, result.get(0).getMinutesToSnoozeFor()); // respect order assertEquals(2, result.get(1).getMinutesToSnoozeFor()); assertEquals(3, result.get(2).getMinutesToSnoozeFor()); - assertEquals(2, mNotificationSnooze.getDefaultOption().getMinutesToSnoozeFor()); + assertEquals(2, mUnderTest.getDefaultOption().getMinutesToSnoozeFor()); } @Test - public void testGetOptionsWithInvalidConfig() throws Exception { + public void testGetOptionsWithInvalidConfig() { Settings.Global.putString(mContext.getContentResolver(), Settings.Global.NOTIFICATION_SNOOZE_OPTIONS, "this is garbage"); - ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions(); + ArrayList<SnoozeOption> result = mUnderTest.getDefaultSnoozeOptions(); assertEquals(3, result.size()); assertEquals(1, result.get(0).getMinutesToSnoozeFor()); // respect order assertEquals(2, result.get(1).getMinutesToSnoozeFor()); assertEquals(3, result.get(2).getMinutesToSnoozeFor()); - assertEquals(2, mNotificationSnooze.getDefaultOption().getMinutesToSnoozeFor()); + assertEquals(2, mUnderTest.getDefaultOption().getMinutesToSnoozeFor()); } @Test - public void testGetOptionsWithValidDefault() throws Exception { + public void testGetOptionsWithValidDefault() { Settings.Global.putString(mContext.getContentResolver(), Settings.Global.NOTIFICATION_SNOOZE_OPTIONS, "default=10,options_array=4:5:6:7"); - ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions(); - assertNotNull(mNotificationSnooze.getDefaultOption()); // pick one + ArrayList<SnoozeOption> result = mUnderTest.getDefaultSnoozeOptions(); + assertNotNull(mUnderTest.getDefaultOption()); // pick one } @Test - public void testGetOptionsWithValidConfig() throws Exception { + public void testGetOptionsWithValidConfig() { Settings.Global.putString(mContext.getContentResolver(), Settings.Global.NOTIFICATION_SNOOZE_OPTIONS, "default=6,options_array=4:5:6:7"); - ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions(); + ArrayList<SnoozeOption> result = mUnderTest.getDefaultSnoozeOptions(); assertEquals(4, result.size()); assertEquals(4, result.get(0).getMinutesToSnoozeFor()); // respect order assertEquals(5, result.get(1).getMinutesToSnoozeFor()); assertEquals(6, result.get(2).getMinutesToSnoozeFor()); assertEquals(7, result.get(3).getMinutesToSnoozeFor()); - assertEquals(6, mNotificationSnooze.getDefaultOption().getMinutesToSnoozeFor()); + assertEquals(6, mUnderTest.getDefaultOption().getMinutesToSnoozeFor()); } @Test - public void testGetOptionsWithLongConfig() throws Exception { + public void testGetOptionsWithLongConfig() { Settings.Global.putString(mContext.getContentResolver(), Settings.Global.NOTIFICATION_SNOOZE_OPTIONS, "default=6,options_array=4:5:6:7:8:9:10:11:12:13:14:15:16:17"); - ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions(); + ArrayList<SnoozeOption> result = mUnderTest.getDefaultSnoozeOptions(); assertTrue(result.size() > 3); assertEquals(4, result.get(0).getMinutesToSnoozeFor()); // respect order assertEquals(5, result.get(1).getMinutesToSnoozeFor()); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt index 09be93de9f3e..ea91b7a9d6e2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.unfold import android.content.Context import android.content.res.Resources import android.hardware.devicestate.DeviceStateManager +import android.os.PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.R @@ -27,16 +28,20 @@ import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImp import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl import com.android.systemui.defaultDeviceState import com.android.systemui.deviceStateManager -import com.android.systemui.display.data.repository.DeviceStateRepository import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState +import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState.FOLDED +import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState.HALF_FOLDED +import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState.UNFOLDED +import com.android.systemui.display.data.repository.fakeDeviceStateRepository import com.android.systemui.foldedDeviceStateList import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.kosmos.Kosmos -import com.android.systemui.power.domain.interactor.PowerInteractor -import com.android.systemui.power.shared.model.ScreenPowerState -import com.android.systemui.power.shared.model.WakeSleepReason -import com.android.systemui.power.shared.model.WakefulnessModel -import com.android.systemui.power.shared.model.WakefulnessState +import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest +import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest +import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setScreenPowerState +import com.android.systemui.power.domain.interactor.PowerInteractorFactory +import com.android.systemui.power.shared.model.ScreenPowerState.SCREEN_OFF +import com.android.systemui.power.shared.model.ScreenPowerState.SCREEN_ON import com.android.systemui.shared.system.SysUiStatsLog import com.android.systemui.statusbar.policy.FakeConfigurationController import com.android.systemui.unfold.DisplaySwitchLatencyTracker.Companion.FOLDABLE_DEVICE_STATE_CLOSED @@ -45,7 +50,7 @@ import com.android.systemui.unfold.DisplaySwitchLatencyTracker.DisplaySwitchLate import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImpl import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor import com.android.systemui.unfoldedDeviceState -import com.android.systemui.util.animation.data.repository.AnimationStatusRepository +import com.android.systemui.util.animation.data.repository.fakeAnimationStatusRepository import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.capture import com.android.systemui.util.time.FakeSystemClock @@ -77,14 +82,15 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { private lateinit var displaySwitchLatencyTracker: DisplaySwitchLatencyTracker @Captor private lateinit var loggerArgumentCaptor: ArgumentCaptor<DisplaySwitchLatencyEvent> + private val kosmos = Kosmos() private val mockContext = mock<Context>() private val resources = mock<Resources>() - private val foldStateRepository = mock<DeviceStateRepository>() - private val powerInteractor = mock<PowerInteractor>() - private val animationStatusRepository = mock<AnimationStatusRepository>() + private val foldStateRepository = kosmos.fakeDeviceStateRepository + private val powerInteractor = PowerInteractorFactory.create().powerInteractor + private val animationStatusRepository = kosmos.fakeAnimationStatusRepository private val keyguardInteractor = mock<KeyguardInteractor>() private val displaySwitchLatencyLogger = mock<DisplaySwitchLatencyLogger>() - private val kosmos = Kosmos() + private val deviceStateManager = kosmos.deviceStateManager private val closedDeviceState = kosmos.foldedDeviceStateList.first() private val openDeviceState = kosmos.unfoldedDeviceState @@ -94,12 +100,7 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { private val testDispatcher: TestDispatcher = StandardTestDispatcher() private val testScope: TestScope = TestScope(testDispatcher) - private val isAsleep = MutableStateFlow(false) private val isAodAvailable = MutableStateFlow(false) - private val deviceState = MutableStateFlow(DeviceState.UNFOLDED) - private val screenPowerState = MutableStateFlow(ScreenPowerState.SCREEN_ON) - private val areAnimationEnabled = MutableStateFlow(true) - private val lastWakefulnessEvent = MutableStateFlow(WakefulnessModel()) private val systemClock = FakeSystemClock() private val configurationController = FakeConfigurationController() private val configurationRepository = @@ -126,13 +127,10 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { .thenReturn(listOf(closedDeviceState, openDeviceState)) whenever(resources.getIntArray(R.array.config_foldedDeviceStates)) .thenReturn(nonEmptyClosedDeviceStatesArray) - whenever(foldStateRepository.state).thenReturn(deviceState) - whenever(powerInteractor.isAsleep).thenReturn(isAsleep) - whenever(animationStatusRepository.areAnimationsEnabled()).thenReturn(areAnimationEnabled) - whenever(powerInteractor.screenPowerState).thenReturn(screenPowerState) whenever(keyguardInteractor.isAodAvailable).thenReturn(isAodAvailable) - whenever(powerInteractor.detailedWakefulness).thenReturn(lastWakefulnessEvent) - + animationStatusRepository.onAnimationStatusChanged(true) + powerInteractor.setAwakeForTest() + powerInteractor.setScreenPowerState(SCREEN_ON) displaySwitchLatencyTracker = DisplaySwitchLatencyTracker( mockContext, @@ -152,21 +150,19 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { @Test fun unfold_logsLatencyTillTransitionStarted() { testScope.runTest { - areAnimationEnabled.emit(true) - displaySwitchLatencyTracker.start() - deviceState.emit(DeviceState.FOLDED) - screenPowerState.emit(ScreenPowerState.SCREEN_OFF) + setDeviceState(FOLDED) + powerInteractor.setScreenPowerState(SCREEN_OFF) systemClock.advanceTime(50) runCurrent() - deviceState.emit(DeviceState.HALF_FOLDED) + setDeviceState(HALF_FOLDED) runCurrent() systemClock.advanceTime(50) - screenPowerState.emit(ScreenPowerState.SCREEN_ON) + powerInteractor.setScreenPowerState(SCREEN_ON) systemClock.advanceTime(200) unfoldTransitionProgressProvider.onTransitionStarted() runCurrent() - deviceState.emit(DeviceState.UNFOLDED) + setDeviceState(UNFOLDED) verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor)) val loggedEvent = loggerArgumentCaptor.value @@ -202,23 +198,22 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { systemClock, deviceStateManager, ) - areAnimationEnabled.emit(true) displaySwitchLatencyTracker.start() - deviceState.emit(DeviceState.FOLDED) - screenPowerState.emit(ScreenPowerState.SCREEN_OFF) + setDeviceState(FOLDED) + powerInteractor.setScreenPowerState(SCREEN_OFF) systemClock.advanceTime(50) runCurrent() - deviceState.emit(DeviceState.HALF_FOLDED) + setDeviceState(HALF_FOLDED) systemClock.advanceTime(50) runCurrent() - screenPowerState.emit(ScreenPowerState.SCREEN_ON) + powerInteractor.setScreenPowerState(SCREEN_ON) systemClock.advanceTime(50) runCurrent() systemClock.advanceTime(200) unfoldTransitionProgressProvider.onTransitionStarted() runCurrent() - deviceState.emit(DeviceState.UNFOLDED) + setDeviceState(UNFOLDED) verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor)) val loggedEvent = loggerArgumentCaptor.value @@ -235,23 +230,23 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { @Test fun unfold_animationDisabled_logsLatencyTillScreenTurnedOn() { testScope.runTest { - areAnimationEnabled.emit(false) + animationStatusRepository.onAnimationStatusChanged(false) displaySwitchLatencyTracker.start() - deviceState.emit(DeviceState.FOLDED) - screenPowerState.emit(ScreenPowerState.SCREEN_OFF) + setDeviceState(FOLDED) + powerInteractor.setScreenPowerState(SCREEN_OFF) systemClock.advanceTime(50) runCurrent() - deviceState.emit(DeviceState.HALF_FOLDED) + setDeviceState(HALF_FOLDED) systemClock.advanceTime(50) runCurrent() - screenPowerState.emit(ScreenPowerState.SCREEN_ON) + powerInteractor.setScreenPowerState(SCREEN_ON) systemClock.advanceTime(50) runCurrent() unfoldTransitionProgressProvider.onTransitionStarted() systemClock.advanceTime(200) runCurrent() - deviceState.emit(DeviceState.UNFOLDED) + setDeviceState(UNFOLDED) verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor)) val loggedEvent = loggerArgumentCaptor.value @@ -268,19 +263,18 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { @Test fun foldWhileStayingAwake_logsLatency() { testScope.runTest { - areAnimationEnabled.emit(true) - deviceState.emit(DeviceState.UNFOLDED) - screenPowerState.emit(ScreenPowerState.SCREEN_ON) + setDeviceState(UNFOLDED) + powerInteractor.setScreenPowerState(SCREEN_ON) displaySwitchLatencyTracker.start() - deviceState.emit(DeviceState.HALF_FOLDED) + setDeviceState(HALF_FOLDED) systemClock.advanceTime(50) runCurrent() - deviceState.emit(DeviceState.FOLDED) - screenPowerState.emit(ScreenPowerState.SCREEN_OFF) + setDeviceState(FOLDED) + powerInteractor.setScreenPowerState(SCREEN_OFF) runCurrent() systemClock.advanceTime(200) - screenPowerState.emit(ScreenPowerState.SCREEN_ON) + powerInteractor.setScreenPowerState(SCREEN_ON) runCurrent() verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor)) @@ -298,25 +292,19 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { @Test fun foldToAod_capturesToStateAsAod() { testScope.runTest { - areAnimationEnabled.emit(true) - deviceState.emit(DeviceState.UNFOLDED) + setDeviceState(UNFOLDED) isAodAvailable.emit(true) displaySwitchLatencyTracker.start() - deviceState.emit(DeviceState.HALF_FOLDED) + setDeviceState(HALF_FOLDED) systemClock.advanceTime(50) runCurrent() - deviceState.emit(DeviceState.FOLDED) - lastWakefulnessEvent.emit( - WakefulnessModel( - internalWakefulnessState = WakefulnessState.ASLEEP, - lastSleepReason = WakeSleepReason.FOLD, - ) - ) - screenPowerState.emit(ScreenPowerState.SCREEN_OFF) + setDeviceState(FOLDED) + powerInteractor.setAsleepForTest(sleepReason = GO_TO_SLEEP_REASON_DEVICE_FOLD) + powerInteractor.setScreenPowerState(SCREEN_OFF) runCurrent() systemClock.advanceTime(200) - screenPowerState.emit(ScreenPowerState.SCREEN_ON) + powerInteractor.setScreenPowerState(SCREEN_ON) runCurrent() verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor)) @@ -335,22 +323,21 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { @Test fun fold_notAFoldable_shouldNotLogLatency() { testScope.runTest { - areAnimationEnabled.emit(true) - deviceState.emit(DeviceState.UNFOLDED) + setDeviceState(UNFOLDED) whenever(resources.getIntArray(R.array.config_foldedDeviceStates)) .thenReturn(IntArray(0)) whenever(deviceStateManager.supportedDeviceStates) .thenReturn(listOf(defaultDeviceState)) displaySwitchLatencyTracker.start() - deviceState.emit(DeviceState.HALF_FOLDED) + setDeviceState(HALF_FOLDED) systemClock.advanceTime(50) runCurrent() - deviceState.emit(DeviceState.FOLDED) - screenPowerState.emit(ScreenPowerState.SCREEN_OFF) + setDeviceState(FOLDED) + powerInteractor.setScreenPowerState(SCREEN_OFF) runCurrent() systemClock.advanceTime(200) - screenPowerState.emit(ScreenPowerState.SCREEN_ON) + powerInteractor.setScreenPowerState(SCREEN_ON) runCurrent() verify(displaySwitchLatencyLogger, never()).log(any()) @@ -360,22 +347,16 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { @Test fun foldToScreenOff_capturesToStateAsScreenOff() { testScope.runTest { - areAnimationEnabled.emit(true) - deviceState.emit(DeviceState.UNFOLDED) + setDeviceState(UNFOLDED) isAodAvailable.emit(false) displaySwitchLatencyTracker.start() - deviceState.emit(DeviceState.HALF_FOLDED) + setDeviceState(HALF_FOLDED) systemClock.advanceTime(50) runCurrent() - deviceState.emit(DeviceState.FOLDED) - lastWakefulnessEvent.emit( - WakefulnessModel( - internalWakefulnessState = WakefulnessState.ASLEEP, - lastSleepReason = WakeSleepReason.FOLD, - ) - ) - screenPowerState.emit(ScreenPowerState.SCREEN_OFF) + setDeviceState(FOLDED) + powerInteractor.setAsleepForTest(sleepReason = GO_TO_SLEEP_REASON_DEVICE_FOLD) + powerInteractor.setScreenPowerState(SCREEN_OFF) runCurrent() verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor)) @@ -390,4 +371,8 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { assertThat(loggedEvent).isEqualTo(expectedLoggedEvent) } } + + private suspend fun setDeviceState(state: DeviceState) { + foldStateRepository.emit(state) + } } diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 3b89e9c42c93..84c859ccd5a9 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1359,6 +1359,8 @@ <string name="hub_onboarding_bottom_sheet_text">Access your favorite widgets and screen savers while charging.</string> <!-- Hub onboarding bottom sheet action button title. [CHAR LIMIT=NONE] --> <string name="hub_onboarding_bottom_sheet_action_button">Let\u2019s go</string> + <!-- Text for a tooltip that appears over the "show screensaver" button on glanceable hub. [CHAR LIMIT=NONE] --> + <string name="glanceable_hub_to_dream_button_tooltip">Show your favorite screensavers while charging</string> <!-- Related to user switcher --><skip/> diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt index 1c994731c393..126471234fa1 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt @@ -32,6 +32,9 @@ import com.android.systemui.authentication.shared.model.AuthenticationWipeModel. import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.logDiffsForTable +import com.android.systemui.scene.domain.SceneFrameworkTableLog import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.time.SystemClock import javax.inject.Inject @@ -66,6 +69,7 @@ constructor( @Background private val backgroundDispatcher: CoroutineDispatcher, private val repository: AuthenticationRepository, private val selectedUserInteractor: SelectedUserInteractor, + @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer, ) { /** * The currently-configured authentication method. This determines how the authentication @@ -85,7 +89,11 @@ constructor( * `true` even when the lockscreen is showing and still needs to be dismissed by the user to * proceed. */ - val authenticationMethod: Flow<AuthenticationMethodModel> = repository.authenticationMethod + val authenticationMethod: Flow<AuthenticationMethodModel> = + repository.authenticationMethod.logDiffsForTable( + tableLogBuffer = tableLogBuffer, + initialValue = AuthenticationMethodModel.None, + ) /** * Whether the auto confirm feature is enabled for the currently-selected user. diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt index 4e45fcc25fb8..744fd7e94ab4 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt @@ -16,6 +16,9 @@ package com.android.systemui.authentication.shared.model +import com.android.systemui.log.table.Diffable +import com.android.systemui.log.table.TableRowLogger + /** Enumerates all known authentication methods. */ sealed class AuthenticationMethodModel( /** @@ -24,8 +27,8 @@ sealed class AuthenticationMethodModel( * "Secure" authentication methods require authentication to unlock the device. Non-secure auth * methods simply require user dismissal. */ - open val isSecure: Boolean, -) { + open val isSecure: Boolean +) : Diffable<AuthenticationMethodModel> { /** * Device doesn't use a secure authentication method. Either there is no lockscreen or the lock * screen can be swiped away when displayed. @@ -39,4 +42,8 @@ sealed class AuthenticationMethodModel( data object Pattern : AuthenticationMethodModel(isSecure = true) data object Sim : AuthenticationMethodModel(isSecure = true) + + override fun logDiffs(prevVal: AuthenticationMethodModel, row: TableRowLogger) { + row.logChange(columnName = "authenticationMethod", value = toString()) + } } diff --git a/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt index 19238804fb12..79e66a89cd6b 100644 --- a/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt +++ b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt @@ -16,6 +16,8 @@ package com.android.systemui.common.data +import com.android.systemui.common.data.repository.BatteryRepository +import com.android.systemui.common.data.repository.BatteryRepositoryImpl import com.android.systemui.common.data.repository.PackageChangeRepository import com.android.systemui.common.data.repository.PackageChangeRepositoryImpl import dagger.Binds @@ -27,4 +29,6 @@ abstract class CommonDataLayerModule { abstract fun bindPackageChangeRepository( impl: PackageChangeRepositoryImpl ): PackageChangeRepository + + @Binds abstract fun bindBatteryRepository(impl: BatteryRepositoryImpl): BatteryRepository } diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/BatteryRepository.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/BatteryRepository.kt new file mode 100644 index 000000000000..63b051339d4b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/BatteryRepository.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 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.systemui.common.data.repository + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.statusbar.policy.BatteryController +import com.android.systemui.util.kotlin.isDevicePluggedIn +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.stateIn + +interface BatteryRepository { + val isDevicePluggedIn: Flow<Boolean> +} + +@SysUISingleton +class BatteryRepositoryImpl +@Inject +constructor(@Background bgScope: CoroutineScope, batteryController: BatteryController) : + BatteryRepository { + + /** Returns {@code true} if the device is currently plugged in or wireless charging. */ + override val isDevicePluggedIn: Flow<Boolean> = + batteryController + .isDevicePluggedIn() + .stateIn(bgScope, SharingStarted.WhileSubscribed(), batteryController.isPluggedIn) +} diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/interactor/BatteryInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/BatteryInteractor.kt new file mode 100644 index 000000000000..987776d14b2b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/BatteryInteractor.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 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.systemui.common.domain.interactor + +import com.android.systemui.common.data.repository.BatteryRepository +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +@SysUISingleton +class BatteryInteractor @Inject constructor(batteryRepository: BatteryRepository) { + val isDevicePluggedIn = batteryRepository.isDevicePluggedIn +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/DevicePosturingListener.kt b/packages/SystemUI/src/com/android/systemui/communal/DevicePosturingListener.kt index 47040fa4a572..af8a5fa23ccb 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/DevicePosturingListener.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/DevicePosturingListener.kt @@ -58,7 +58,6 @@ constructor( .distinctUntilChanged() .logDiffsForTable( tableLogBuffer = tableLogBuffer, - columnPrefix = "", columnName = "postured", initialValue = false, ) diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt index 882991aacdbb..b89d32244e17 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt @@ -52,7 +52,6 @@ constructor( override val mediaModel: Flow<CommunalMediaModel> = _mediaModel.logDiffsForTable( tableLogBuffer = tableLogBuffer, - columnPrefix = "", initialValue = CommunalMediaModel.INACTIVE, ) diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt index 090264678f35..b7476900d784 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt @@ -56,6 +56,12 @@ interface CommunalPrefsRepository { /** Save the hub onboarding dismissed state for the current user. */ suspend fun setHubOnboardingDismissed(user: UserInfo) + + /** Whether dream button tooltip has been dismissed. */ + fun isDreamButtonTooltipDismissed(user: UserInfo): Flow<Boolean> + + /** Save the dream button tooltip dismissed state for the current user. */ + suspend fun setDreamButtonTooltipDismissed(user: UserInfo) } @SysUISingleton @@ -87,27 +93,34 @@ constructor( readKeyForUser(user, CTA_DISMISSED_STATE) override suspend fun setCtaDismissed(user: UserInfo) = - withContext(bgDispatcher) { - getSharedPrefsForUser(user).edit().putBoolean(CTA_DISMISSED_STATE, true).apply() - logger.i("Dismissed CTA tile") - } + setBooleanKeyValueForUser(user, CTA_DISMISSED_STATE, "Dismissed CTA tile") override fun isHubOnboardingDismissed(user: UserInfo): Flow<Boolean> = readKeyForUser(user, HUB_ONBOARDING_DISMISSED_STATE) override suspend fun setHubOnboardingDismissed(user: UserInfo) = - withContext(bgDispatcher) { - getSharedPrefsForUser(user) - .edit() - .putBoolean(HUB_ONBOARDING_DISMISSED_STATE, true) - .apply() - logger.i("Dismissed hub onboarding") - } + setBooleanKeyValueForUser(user, HUB_ONBOARDING_DISMISSED_STATE, "Dismissed hub onboarding") + + override fun isDreamButtonTooltipDismissed(user: UserInfo): Flow<Boolean> = + readKeyForUser(user, DREAM_BUTTON_TOOLTIP_DISMISSED_STATE) + + override suspend fun setDreamButtonTooltipDismissed(user: UserInfo) = + setBooleanKeyValueForUser( + user, + DREAM_BUTTON_TOOLTIP_DISMISSED_STATE, + "Dismissed dream button tooltip", + ) private fun getSharedPrefsForUser(user: UserInfo): SharedPreferences { return userFileManager.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE, user.id) } + private suspend fun setBooleanKeyValueForUser(user: UserInfo, key: String, logMsg: String) = + withContext(bgDispatcher) { + getSharedPrefsForUser(user).edit().putBoolean(key, true).apply() + logger.i(logMsg) + } + private fun readKeyForUser(user: UserInfo, key: String): Flow<Boolean> { return backupRestorationEvents .flatMapLatest { @@ -122,5 +135,6 @@ constructor( const val FILE_NAME = "communal_hub_prefs" const val CTA_DISMISSED_STATE = "cta_dismissed" const val HUB_ONBOARDING_DISMISSED_STATE = "hub_onboarding_dismissed" + const val DREAM_BUTTON_TOOLTIP_DISMISSED_STATE = "dream_button_tooltip_dismissed_state" } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt index 53122c56ed2c..abd101693b43 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt @@ -34,6 +34,7 @@ import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_I import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_USER_SETTING import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryModule.Companion.DEFAULT_BACKGROUND_TYPE import com.android.systemui.communal.shared.model.CommunalBackgroundType +import com.android.systemui.communal.shared.model.WhenToDream import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -60,6 +61,12 @@ interface CommunalSettingsRepository { fun getScreensaverEnabledState(user: UserInfo): Flow<Boolean> /** + * Returns a [WhenToDream] for the specified user, indicating what state the device should be in + * to trigger dreams. + */ + fun getWhenToDreamState(user: UserInfo): Flow<WhenToDream> + + /** * Returns true if any glanceable hub functionality should be enabled via configs and flags. * * This should be used for preventing basic glanceable hub functionality from running on devices @@ -157,6 +164,49 @@ constructor( } .flowOn(bgDispatcher) + override fun getWhenToDreamState(user: UserInfo): Flow<WhenToDream> = + secureSettings + .observerFlow( + userId = user.id, + names = + arrayOf( + Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, + Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, + Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED, + ), + ) + .emitOnStart() + .map { + if ( + secureSettings.getIntForUser( + Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, + 0, + user.id, + ) == 1 + ) { + WhenToDream.WHILE_CHARGING + } else if ( + secureSettings.getIntForUser( + Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, + 0, + user.id, + ) == 1 + ) { + WhenToDream.WHILE_DOCKED + } else if ( + secureSettings.getIntForUser( + Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED, + 0, + user.id, + ) == 1 + ) { + WhenToDream.WHILE_POSTURED + } else { + WhenToDream.NEVER + } + } + .flowOn(bgDispatcher) + override fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean> = broadcastDispatcher .broadcastFlow( diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt index 19666e4df1cf..1b0a6a06caa3 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt @@ -98,7 +98,6 @@ constructor( .filterNotNull() .logDiffsForTable( tableLogBuffer = tableLogBuffer, - columnPrefix = "", columnName = "tutorialSettingState", initialValue = HUB_MODE_TUTORIAL_NOT_STARTED, ) diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index 74c335e79d4e..b4e6e9348b3d 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -30,11 +30,13 @@ import com.android.compose.animation.scene.TransitionKey import com.android.systemui.Flags.communalResponsiveGrid import com.android.systemui.Flags.glanceableHubBlurredBackground import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.common.domain.interactor.BatteryInteractor import com.android.systemui.communal.data.repository.CommunalMediaRepository import com.android.systemui.communal.data.repository.CommunalSmartspaceRepository import com.android.systemui.communal.data.repository.CommunalWidgetRepository import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.domain.model.CommunalContentModel.WidgetContent +import com.android.systemui.communal.posturing.domain.interactor.PosturingInteractor import com.android.systemui.communal.shared.model.CommunalBackgroundType import com.android.systemui.communal.shared.model.CommunalContentSize import com.android.systemui.communal.shared.model.CommunalContentSize.FixedSize.FULL @@ -43,11 +45,14 @@ import com.android.systemui.communal.shared.model.CommunalContentSize.FixedSize. import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalWidgetContentModel import com.android.systemui.communal.shared.model.EditModeState +import com.android.systemui.communal.shared.model.WhenToDream import com.android.systemui.communal.widgets.EditWidgetsActivityStarter import com.android.systemui.communal.widgets.WidgetConfigurator import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dock.DockManager +import com.android.systemui.dock.retrieveIsDocked import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.Edge @@ -67,6 +72,7 @@ import com.android.systemui.statusbar.phone.ManagedProfileController import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf import com.android.systemui.util.kotlin.BooleanFlowOperators.not import com.android.systemui.util.kotlin.emitOnStart +import com.android.systemui.util.kotlin.isDevicePluggedIn import javax.inject.Inject import kotlin.time.Duration.Companion.minutes import kotlinx.coroutines.CoroutineDispatcher @@ -86,6 +92,7 @@ import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -117,6 +124,9 @@ constructor( @CommunalLog logBuffer: LogBuffer, @CommunalTableLog tableLogBuffer: TableLogBuffer, private val managedProfileController: ManagedProfileController, + private val batteryInteractor: BatteryInteractor, + private val dockManager: DockManager, + private val posturingInteractor: PosturingInteractor, ) { private val logger = Logger(logBuffer, "CommunalInteractor") @@ -163,7 +173,6 @@ constructor( } .logDiffsForTable( tableLogBuffer = tableLogBuffer, - columnPrefix = "", columnName = "isCommunalAvailable", initialValue = false, ) @@ -173,6 +182,33 @@ constructor( replay = 1, ) + /** + * Whether communal hub should be shown automatically, depending on the user's [WhenToDream] + * state. + */ + val shouldShowCommunal: Flow<Boolean> = + allOf( + isCommunalAvailable, + communalSettingsInteractor.whenToDream + .flatMapLatest { whenToDream -> + when (whenToDream) { + WhenToDream.NEVER -> flowOf(false) + + WhenToDream.WHILE_CHARGING -> batteryInteractor.isDevicePluggedIn + + WhenToDream.WHILE_DOCKED -> + allOf( + batteryInteractor.isDevicePluggedIn, + dockManager.retrieveIsDocked(), + ) + + WhenToDream.WHILE_POSTURED -> + allOf(batteryInteractor.isDevicePluggedIn, posturingInteractor.postured) + } + } + .flowOn(bgDispatcher), + ) + private val _isDisclaimerDismissed = MutableStateFlow(false) val isDisclaimerDismissed: Flow<Boolean> = _isDisclaimerDismissed.asStateFlow() @@ -300,7 +336,6 @@ constructor( } .logDiffsForTable( tableLogBuffer = tableLogBuffer, - columnPrefix = "", columnName = "isCommunalShowing", initialValue = false, ) diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractor.kt index ec45d6c8d545..cdf17033cc01 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalPrefsInteractor.kt @@ -49,7 +49,6 @@ constructor( .flatMapLatest { user -> repository.isCtaDismissed(user) } .logDiffsForTable( tableLogBuffer = tableLogBuffer, - columnPrefix = "", columnName = "isCtaDismissed", initialValue = false, ) @@ -67,7 +66,6 @@ constructor( .flatMapLatest { user -> repository.isHubOnboardingDismissed(user) } .logDiffsForTable( tableLogBuffer = tableLogBuffer, - columnPrefix = "", columnName = "isHubOnboardingDismissed", initialValue = false, ) @@ -80,6 +78,24 @@ constructor( fun setHubOnboardingDismissed(user: UserInfo = userTracker.userInfo) = bgScope.launch { repository.setHubOnboardingDismissed(user) } + val isDreamButtonTooltipDismissed: Flow<Boolean> = + userInteractor.selectedUserInfo + .flatMapLatest { user -> repository.isDreamButtonTooltipDismissed(user) } + .logDiffsForTable( + tableLogBuffer = tableLogBuffer, + columnPrefix = "", + columnName = "isDreamButtonTooltipDismissed", + initialValue = false, + ) + .stateIn( + scope = bgScope, + started = SharingStarted.WhileSubscribed(), + initialValue = false, + ) + + fun setDreamButtonTooltipDismissed(user: UserInfo = userTracker.userInfo) = + bgScope.launch { repository.setDreamButtonTooltipDismissed(user) } + private companion object { const val TAG = "CommunalPrefsInteractor" } diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt index 1738f37b7f0c..a0b1261df346 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt @@ -21,6 +21,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.communal.data.model.CommunalEnabledState import com.android.systemui.communal.data.repository.CommunalSettingsRepository import com.android.systemui.communal.shared.model.CommunalBackgroundType +import com.android.systemui.communal.shared.model.WhenToDream import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.log.dagger.CommunalTableLog @@ -73,6 +74,12 @@ constructor( repository.getScreensaverEnabledState(user) } + /** When to dream for the currently selected user. */ + val whenToDream: Flow<WhenToDream> = + userInteractor.selectedUserInfo.flatMapLatest { user -> + repository.getWhenToDreamState(user) + } + /** * Returns true if any glanceable hub functionality should be enabled via configs and flags. * diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt index 4e4ecc949d09..8f55d96e947a 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt @@ -65,7 +65,6 @@ constructor( } .logDiffsForTable( tableLogBuffer = tableLogBuffer, - columnPrefix = "", columnName = "isTutorialAvailable", initialValue = false, ) diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/WhenToDream.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/WhenToDream.kt new file mode 100644 index 000000000000..0d4eb60c5240 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/WhenToDream.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 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.systemui.communal.shared.model + +enum class WhenToDream { + NEVER, + WHILE_CHARGING, + WHILE_DOCKED, + WHILE_POSTURED, +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt index bbb168680221..7e683c45e525 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt @@ -20,11 +20,14 @@ import android.annotation.SuppressLint import android.app.DreamManager import android.content.Intent import android.provider.Settings +import androidx.compose.runtime.getValue import com.android.internal.logging.UiEventLogger +import com.android.systemui.communal.domain.interactor.CommunalPrefsInteractor import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.communal.shared.log.CommunalUiEvent import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.lifecycle.ExclusiveActivatable +import com.android.systemui.lifecycle.Hydrator import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.util.kotlin.isDevicePluggedIn @@ -37,7 +40,7 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -47,18 +50,36 @@ class CommunalToDreamButtonViewModel constructor( @Background private val backgroundContext: CoroutineContext, batteryController: BatteryController, + private val prefsInteractor: CommunalPrefsInteractor, private val settingsInteractor: CommunalSettingsInteractor, private val activityStarter: ActivityStarter, private val dreamManager: DreamManager, private val uiEventLogger: UiEventLogger, ) : ExclusiveActivatable() { + private val hydrator = Hydrator("CommunalToDreamButtonViewModel.hydrator") private val _requests = Channel<Unit>(Channel.BUFFERED) /** Whether we should show a button on hub to switch to dream. */ - @SuppressLint("MissingPermission") - val shouldShowDreamButtonOnHub = - batteryController.isDevicePluggedIn().distinctUntilChanged().flowOn(backgroundContext) + val shouldShowDreamButtonOnHub: Boolean by + hydrator.hydratedStateOf( + traceName = "shouldShowDreamButtonOnHub", + initialValue = false, + source = batteryController.isDevicePluggedIn().distinctUntilChanged(), + ) + + /** Return whether the dream button tooltip has been dismissed. */ + val shouldShowTooltip: Boolean by + hydrator.hydratedStateOf( + traceName = "shouldShowTooltip", + initialValue = false, + source = prefsInteractor.isDreamButtonTooltipDismissed.map { !it }, + ) + + /** Set the dream button tooltip to be dismissed. */ + fun setDreamButtonTooltipDismissed() { + prefsInteractor.setDreamButtonTooltipDismissed() + } /** Handle a tap on the "show dream" button. */ fun onShowDreamButtonTap() { @@ -86,6 +107,8 @@ constructor( } } + launch { hydrator.activate() } + awaitCancellation() } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt index 1e7bec257432..69da67e055fe 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt @@ -68,4 +68,10 @@ constructor( emptyFlow() } } + + /** Triggered if a face failure occurs regardless of the mode. */ + val faceFailure: Flow<FailedFaceAuthenticationStatus> = + deviceEntryFaceAuthInteractor.authenticationStatus.filterIsInstance< + FailedFaceAuthenticationStatus + >() } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt index cdd2b054711e..079d624e6fe0 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt @@ -19,6 +19,7 @@ package com.android.systemui.deviceentry.domain.interactor import com.android.systemui.CoreStartable import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus +import com.android.systemui.log.table.TableLogBuffer import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @@ -81,6 +82,8 @@ interface DeviceEntryFaceAuthInteractor : CoreStartable { /** Whether face auth is considered class 3 */ fun isFaceAuthStrong(): Boolean + + suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) } /** @@ -93,17 +96,17 @@ interface DeviceEntryFaceAuthInteractor : CoreStartable { */ interface FaceAuthenticationListener { /** Receive face isAuthenticated updates */ - fun onAuthenticatedChanged(isAuthenticated: Boolean) + fun onAuthenticatedChanged(isAuthenticated: Boolean) = Unit /** Receive face authentication status updates */ - fun onAuthenticationStatusChanged(status: FaceAuthenticationStatus) + fun onAuthenticationStatusChanged(status: FaceAuthenticationStatus) = Unit /** Receive status updates whenever face detection runs */ - fun onDetectionStatusChanged(status: FaceDetectionStatus) + fun onDetectionStatusChanged(status: FaceDetectionStatus) = Unit - fun onLockoutStateChanged(isLockedOut: Boolean) + fun onLockoutStateChanged(isLockedOut: Boolean) = Unit - fun onRunningStateChanged(isRunning: Boolean) + fun onRunningStateChanged(isRunning: Boolean) = Unit - fun onAuthEnrollmentStateChanged(enrolled: Boolean) + fun onAuthEnrollmentStateChanged(enrolled: Boolean) = Unit } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt index cd456a618c48..38e0503440f9 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt @@ -123,7 +123,7 @@ constructor( private val playErrorHapticForBiometricFailure: Flow<Unit> = merge( deviceEntryFingerprintAuthInteractor.fingerprintFailure, - deviceEntryBiometricAuthInteractor.faceOnlyFaceFailure, + deviceEntryBiometricAuthInteractor.faceFailure, ) // map to Unit .map {} diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt index 4ddc98cd434f..5b6859761705 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt @@ -25,7 +25,10 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository import com.android.systemui.keyguard.DismissCallbackRegistry +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.scene.data.model.asIterable +import com.android.systemui.scene.domain.SceneFrameworkTableLog import com.android.systemui.scene.domain.interactor.SceneBackInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.Scenes @@ -33,9 +36,11 @@ import com.android.systemui.util.kotlin.pairwise import com.android.systemui.utils.coroutines.flow.mapLatestConflated import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter @@ -43,6 +48,7 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch /** * Hosts application business logic related to device entry. @@ -62,6 +68,7 @@ constructor( private val alternateBouncerInteractor: AlternateBouncerInteractor, private val dismissCallbackRegistry: DismissCallbackRegistry, sceneBackInteractor: SceneBackInteractor, + @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer, ) { /** * Whether the device is unlocked. @@ -147,6 +154,11 @@ constructor( ) { enteredDirectly, enteredOnBackStack -> enteredOnBackStack || enteredDirectly } + .logDiffsForTable( + tableLogBuffer = tableLogBuffer, + columnName = "isDeviceEntered", + initialValue = false, + ) .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, @@ -184,6 +196,11 @@ constructor( deviceUnlockStatus.deviceUnlockSource?.dismissesLockscreen == false)) && !isDeviceEntered } + .logDiffsForTable( + tableLogBuffer = tableLogBuffer, + columnName = "canSwipeToEnter", + initialValue = false, + ) .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, @@ -271,4 +288,29 @@ constructor( fun lockNow() { deviceUnlockedInteractor.lockNow() } + + suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) { + coroutineScope { + launch { + isDeviceEntered + .logDiffsForTable( + tableLogBuffer = tableLogBuffer, + columnName = "isDeviceEntered", + initialValue = isDeviceEntered.value, + ) + .collect() + } + + launch { + canSwipeToEnter + .map { it?.toString() ?: "" } + .logDiffsForTable( + tableLogBuffer = tableLogBuffer, + columnName = "canSwipeToEnter", + initialValue = canSwipeToEnter.value?.toString() ?: "", + ) + .collect() + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt index 68aef521be7b..b1be9a209a0a 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt @@ -33,8 +33,11 @@ import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.TrustInteractor import com.android.systemui.lifecycle.ExclusiveActivatable +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.power.shared.model.WakeSleepReason +import com.android.systemui.scene.domain.SceneFrameworkTableLog import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated @@ -48,6 +51,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged @@ -74,6 +78,7 @@ constructor( private val systemPropertiesHelper: SystemPropertiesHelper, private val userAwareSecureSettingsRepository: UserAwareSecureSettingsRepository, private val keyguardInteractor: KeyguardInteractor, + @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer, ) : ExclusiveActivatable() { private val deviceUnlockSource = @@ -179,17 +184,33 @@ constructor( private val lockNowRequests = Channel<Unit>() override suspend fun onActivated(): Nothing { - authenticationInteractor.authenticationMethod.collectLatest { authMethod -> - if (!authMethod.isSecure) { - // Device remains unlocked as long as the authentication method is not secure. - Log.d(TAG, "remaining unlocked because auth method not secure") - repository.deviceUnlockStatus.value = DeviceUnlockStatus(true, null) - } else if (authMethod == AuthenticationMethodModel.Sim) { - // Device remains locked while SIM is locked. - Log.d(TAG, "remaining locked because SIM locked") - repository.deviceUnlockStatus.value = DeviceUnlockStatus(false, null) - } else { - handleLockAndUnlockEvents() + coroutineScope { + launch { + authenticationInteractor.authenticationMethod.collectLatest { authMethod -> + if (!authMethod.isSecure) { + // Device remains unlocked as long as the authentication method is not + // secure. + Log.d(TAG, "remaining unlocked because auth method not secure") + repository.deviceUnlockStatus.value = DeviceUnlockStatus(true, null) + } else if (authMethod == AuthenticationMethodModel.Sim) { + // Device remains locked while SIM is locked. + Log.d(TAG, "remaining locked because SIM locked") + repository.deviceUnlockStatus.value = DeviceUnlockStatus(false, null) + } else { + handleLockAndUnlockEvents() + } + } + } + + launch { + deviceUnlockStatus + .map { it.isUnlocked } + .logDiffsForTable( + tableLogBuffer = tableLogBuffer, + columnName = "isUnlocked", + initialValue = deviceUnlockStatus.value.isUnlocked, + ) + .collect() } } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt index 9b8c2b1acc33..ecc4dbc2326a 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt @@ -19,6 +19,7 @@ package com.android.systemui.deviceentry.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus +import com.android.systemui.log.table.TableLogBuffer import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -73,4 +74,6 @@ class NoopDeviceEntryFaceAuthInteractor @Inject constructor() : DeviceEntryFaceA override fun onWalletLaunched() = Unit override fun onDeviceUnfolded() {} + + override suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) {} } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt index b19b2d9ece02..4b90e1d52ea0 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt @@ -44,6 +44,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.KeyguardState.OFF import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.log.FaceAuthenticationLogger +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.SceneInteractor @@ -53,13 +55,16 @@ import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.kotlin.pairwise import com.android.systemui.util.kotlin.sample +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull @@ -379,6 +384,27 @@ constructor( .launchIn(applicationScope) } + override suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) { + conflatedCallbackFlow { + val listener = + object : FaceAuthenticationListener { + override fun onAuthEnrollmentStateChanged(enrolled: Boolean) { + trySend(isFaceAuthEnabledAndEnrolled()) + } + } + + registerListener(listener) + + awaitClose { unregisterListener(listener) } + } + .logDiffsForTable( + tableLogBuffer = tableLogBuffer, + columnName = "isFaceAuthEnabledAndEnrolled", + initialValue = isFaceAuthEnabledAndEnrolled(), + ) + .collect() + } + companion object { const val TAG = "DeviceEntryFaceAuthInteractor" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index 8c6037107c5a..cf712f111034 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -19,8 +19,12 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import android.util.MathUtils import com.android.app.animation.Interpolators +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.Flags.communalSceneKtfRefactor +import com.android.systemui.communal.domain.interactor.CommunalInteractor +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor +import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -39,6 +43,10 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.util.kotlin.sample +import java.util.UUID +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow @@ -47,11 +55,6 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart -import kotlin.time.Duration.Companion.milliseconds -import kotlin.time.Duration.Companion.seconds -import java.util.UUID -import javax.inject.Inject -import com.android.app.tracing.coroutines.launchTraced as launch @SysUISingleton class FromLockscreenTransitionInteractor @@ -68,6 +71,8 @@ constructor( powerInteractor: PowerInteractor, private val glanceableHubTransitions: GlanceableHubTransitions, private val communalSettingsInteractor: CommunalSettingsInteractor, + private val communalInteractor: CommunalInteractor, + private val communalSceneInteractor: CommunalSceneInteractor, private val swipeToDismissInteractor: SwipeToDismissInteractor, keyguardOcclusionInteractor: KeyguardOcclusionInteractor, ) : @@ -94,6 +99,9 @@ constructor( if (!communalSceneKtfRefactor()) { listenForLockscreenToGlanceableHub() } + if (communalSettingsInteractor.isV2FlagEnabled()) { + listenForLockscreenToGlanceableHubV2() + } } /** @@ -268,9 +276,7 @@ constructor( it.transitionState == TransitionState.CANCELED && it.to == KeyguardState.PRIMARY_BOUNCER } - .collect { - transitionId = null - } + .collect { transitionId = null } } } @@ -370,6 +376,19 @@ constructor( } } + private fun listenForLockscreenToGlanceableHubV2() { + scope.launch { + communalInteractor.shouldShowCommunal + .filterRelevantKeyguardStateAnd { shouldShow -> shouldShow } + .collect { + communalSceneInteractor.changeScene( + newScene = CommunalScenes.Communal, + loggingReason = "lockscreen to communal", + ) + } + } + } + override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator { return ValueAnimator().apply { interpolator = Interpolators.LINEAR diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt index 42cbd7d39248..a1f288edcdd3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt @@ -24,6 +24,8 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.kotlin.sample @@ -32,6 +34,7 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -166,4 +169,14 @@ constructor( isKeyguardEnabled.value && lockPatternUtils.isLockScreenDisabled(userId) } } + + suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) { + isKeyguardEnabled + .logDiffsForTable( + tableLogBuffer = tableLogBuffer, + columnName = "isKeyguardEnabled", + initialValue = isKeyguardEnabled.value, + ) + .collect() + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 75178f0ffef0..3739d17da6c4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -42,6 +42,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag @@ -60,6 +62,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combineTransform import kotlinx.coroutines.flow.debounce @@ -533,6 +536,16 @@ constructor( repository.setNotificationStackAbsoluteBottom(bottom) } + suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) { + isDozing + .logDiffsForTable( + tableLogBuffer = tableLogBuffer, + columnName = "isDozing", + initialValue = isDozing.value, + ) + .collect() + } + companion object { private const val TAG = "KeyguardInteractor" /** diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt index f15a7b30dce7..f8d442de0f55 100644 --- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt @@ -22,6 +22,8 @@ import com.android.systemui.camera.CameraGestureHelper import com.android.systemui.classifier.FalsingCollector import com.android.systemui.classifier.FalsingCollectorActual import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.power.data.repository.PowerRepository import com.android.systemui.power.shared.model.DozeScreenStateModel @@ -35,6 +37,7 @@ import javax.inject.Provider import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map @@ -228,6 +231,15 @@ constructor( repository.updateWakefulness(powerButtonLaunchGestureTriggered = true) } + suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) { + detailedWakefulness + .logDiffsForTable( + tableLogBuffer = tableLogBuffer, + initialValue = detailedWakefulness.value, + ) + .collect() + } + companion object { private const val FSI_WAKE_WHY = "full_screen_intent" diff --git a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt index 0f49c94c3195..297c6af5a4a7 100644 --- a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt +++ b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt @@ -1,6 +1,8 @@ package com.android.systemui.power.shared.model import com.android.systemui.keyguard.KeyguardService +import com.android.systemui.log.table.Diffable +import com.android.systemui.log.table.TableRowLogger /** * Models whether the device is awake or asleep, along with information about why we're in that @@ -35,7 +37,7 @@ data class WakefulnessModel( * to a subsequent power gesture. */ val powerButtonLaunchGestureTriggered: Boolean = false, -) { +) : Diffable<WakefulnessModel> { fun isAwake() = internalWakefulnessState == WakefulnessState.AWAKE || internalWakefulnessState == WakefulnessState.STARTING_TO_WAKE @@ -58,4 +60,8 @@ data class WakefulnessModel( return isAwake() && (lastWakeReason == WakeSleepReason.TAP || lastWakeReason == WakeSleepReason.GESTURE) } + + override fun logDiffs(prevVal: WakefulnessModel, row: TableRowLogger) { + row.logChange(columnName = "wakefulness", value = toString()) + } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/SceneDomainModule.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/SceneDomainModule.kt index be792df340c9..f2f237ac987e 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/SceneDomainModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/SceneDomainModule.kt @@ -16,13 +16,27 @@ package com.android.systemui.scene.domain +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.TableLogBufferFactory import com.android.systemui.scene.domain.resolver.SceneResolverModule import dagger.Module +import dagger.Provides +import javax.inject.Qualifier -@Module( - includes = - [ - SceneResolverModule::class, - ] -) -object SceneDomainModule +@Module(includes = [SceneResolverModule::class]) +object SceneDomainModule { + + @JvmStatic + @Provides + @SysUISingleton + @SceneFrameworkTableLog + fun provideSceneFrameworkTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer { + return factory.create("SceneFrameworkTableLog", 100) + } +} + +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class SceneFrameworkTableLog diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt index bebd398ac972..c9d8e0244d20 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt @@ -18,11 +18,16 @@ package com.android.systemui.scene.domain.interactor import com.android.compose.animation.scene.SceneKey import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.table.Diffable +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.TableRowLogger import com.android.systemui.scene.data.model.SceneStack +import com.android.systemui.scene.data.model.asIterable import com.android.systemui.scene.data.model.peek import com.android.systemui.scene.data.model.pop import com.android.systemui.scene.data.model.push import com.android.systemui.scene.data.model.sceneStackOf +import com.android.systemui.scene.domain.SceneFrameworkTableLog import com.android.systemui.scene.shared.logger.SceneLogger import com.android.systemui.scene.shared.model.SceneContainerConfig import javax.inject.Inject @@ -39,6 +44,7 @@ class SceneBackInteractor constructor( private val logger: SceneLogger, private val sceneContainerConfig: SceneContainerConfig, + @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer, ) { private val _backStack = MutableStateFlow(sceneStackOf()) val backStack: StateFlow<SceneStack> = _backStack.asStateFlow() @@ -58,6 +64,7 @@ constructor( fun onSceneChange(from: SceneKey, to: SceneKey) { check(from != to) { "from == to, from=${from.debugName}, to=${to.debugName}" } + val prevVal = backStack.value _backStack.update { stack -> when (stackOperation(from, to, stack)) { null -> stack @@ -68,12 +75,21 @@ constructor( } } logger.logSceneBackStack(backStack.value) + tableLogBuffer.logDiffs( + prevVal = DiffableSceneStack(prevVal), + newVal = DiffableSceneStack(backStack.value), + ) } /** Applies the given [transform] to the back stack. */ fun updateBackStack(transform: (SceneStack) -> SceneStack) { + val prevVal = backStack.value _backStack.update { stack -> transform(stack) } logger.logSceneBackStack(backStack.value) + tableLogBuffer.logDiffs( + prevVal = DiffableSceneStack(prevVal), + newVal = DiffableSceneStack(backStack.value), + ) } private fun stackOperation(from: SceneKey, to: SceneKey, stack: SceneStack): StackOperation? { @@ -106,4 +122,15 @@ constructor( private data object Push : StackOperation private data object Pop : StackOperation + + private class DiffableSceneStack(private val sceneStack: SceneStack) : + Diffable<DiffableSceneStack> { + + override fun logDiffs(prevVal: DiffableSceneStack, row: TableRowLogger) { + row.logChange( + columnName = "backStack", + value = sceneStack.asIterable().joinToString { it.debugName }, + ) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt index 8bc9d96c064a..9c04323f2a0e 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt @@ -27,14 +27,19 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor +import com.android.systemui.log.table.Diffable +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.TableRowLogger import com.android.systemui.scene.data.repository.SceneContainerRepository import com.android.systemui.scene.domain.resolver.SceneResolver import com.android.systemui.scene.shared.logger.SceneLogger import com.android.systemui.scene.shared.model.SceneFamilies import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.util.kotlin.pairwise import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -47,6 +52,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch /** * Generic business logic and app state accessors for the scene framework. @@ -562,6 +568,28 @@ constructor( decrementActiveTransitionAnimationCount() } + suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) { + coroutineScope { + launch { + currentScene + .map { sceneKey -> DiffableSceneKey(key = sceneKey) } + .pairwise() + .collect { (prev, current) -> + tableLogBuffer.logDiffs(prevVal = prev, newVal = current) + } + } + + launch { + currentOverlays + .map { overlayKeys -> DiffableOverlayKeys(keys = overlayKeys) } + .pairwise() + .collect { (prev, current) -> + tableLogBuffer.logDiffs(prevVal = prev, newVal = current) + } + } + } + } + private fun decrementActiveTransitionAnimationCount() { repository.activeTransitionAnimationCount.update { current -> (current - 1).also { @@ -573,4 +601,20 @@ constructor( } } } + + private class DiffableSceneKey(private val key: SceneKey) : Diffable<DiffableSceneKey> { + override fun logDiffs(prevVal: DiffableSceneKey, row: TableRowLogger) { + row.logChange(columnName = "currentScene", value = key.debugName) + } + } + + private class DiffableOverlayKeys(private val keys: Set<OverlayKey>) : + Diffable<DiffableOverlayKeys> { + override fun logDiffs(prevVal: DiffableOverlayKeys, row: TableRowLogger) { + row.logChange( + columnName = "currentOverlays", + value = keys.joinToString { key -> key.debugName }, + ) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index 8602884ec4ee..2fd584176220 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -45,6 +45,7 @@ import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor.Companion.keyguardScenes +import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.model.SceneContainerPlugin import com.android.systemui.model.SysUiState import com.android.systemui.model.updateFlags @@ -54,6 +55,7 @@ import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.power.shared.model.WakeSleepReason import com.android.systemui.scene.data.model.asIterable import com.android.systemui.scene.data.model.sceneStackOf +import com.android.systemui.scene.domain.SceneFrameworkTableLog import com.android.systemui.scene.domain.interactor.DisabledContentInteractor import com.android.systemui.scene.domain.interactor.SceneBackInteractor import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor @@ -145,6 +147,7 @@ constructor( private val disabledContentInteractor: DisabledContentInteractor, private val activityTransitionAnimator: ActivityTransitionAnimator, private val shadeModeInteractor: ShadeModeInteractor, + @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer, ) : CoreStartable { private val centralSurfaces: CentralSurfaces? get() = centralSurfacesOptLazy.get().getOrNull() @@ -154,6 +157,7 @@ constructor( override fun start() { if (SceneContainerFlag.isEnabled) { sceneLogger.logFrameworkEnabled(isEnabled = true) + applicationScope.launch { hydrateTableLogBuffer() } hydrateVisibility() automaticallySwitchScenes() hydrateSystemUiState() @@ -224,6 +228,16 @@ constructor( } } + private suspend fun hydrateTableLogBuffer() { + coroutineScope { + launch { sceneInteractor.hydrateTableLogBuffer(tableLogBuffer) } + launch { keyguardEnabledInteractor.hydrateTableLogBuffer(tableLogBuffer) } + launch { faceUnlockInteractor.hydrateTableLogBuffer(tableLogBuffer) } + launch { powerInteractor.hydrateTableLogBuffer(tableLogBuffer) } + launch { keyguardInteractor.hydrateTableLogBuffer(tableLogBuffer) } + } + } + private fun resetShadeSessions() { applicationScope.launch { combine( diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt index 59d812403777..01451502b859 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt @@ -19,6 +19,9 @@ package com.android.systemui.shade.domain.interactor import android.provider.Settings import androidx.annotation.FloatRange import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.logDiffsForTable +import com.android.systemui.scene.domain.SceneFrameworkTableLog import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.shade.shared.flag.DualShade import com.android.systemui.shade.shared.model.ShadeMode @@ -81,8 +84,9 @@ class ShadeModeInteractorImpl @Inject constructor( @Application applicationScope: CoroutineScope, - repository: ShadeRepository, + private val repository: ShadeRepository, secureSettingsRepository: SecureSettingsRepository, + @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer, ) : ShadeModeInteractor { private val isDualShadeEnabled: Flow<Boolean> = @@ -93,17 +97,17 @@ constructor( override val isShadeLayoutWide: StateFlow<Boolean> = repository.isShadeLayoutWide + private val shadeModeInitialValue: ShadeMode + get() = + determineShadeMode( + isDualShadeEnabled = DUAL_SHADE_ENABLED_DEFAULT, + isShadeLayoutWide = repository.isShadeLayoutWide.value, + ) + override val shadeMode: StateFlow<ShadeMode> = combine(isDualShadeEnabled, repository.isShadeLayoutWide, ::determineShadeMode) - .stateIn( - applicationScope, - SharingStarted.Eagerly, - initialValue = - determineShadeMode( - isDualShadeEnabled = DUAL_SHADE_ENABLED_DEFAULT, - isShadeLayoutWide = repository.isShadeLayoutWide.value, - ), - ) + .logDiffsForTable(tableLogBuffer = tableLogBuffer, initialValue = shadeModeInitialValue) + .stateIn(applicationScope, SharingStarted.Eagerly, initialValue = shadeModeInitialValue) @FloatRange(from = 0.0, to = 1.0) override fun getTopEdgeSplitFraction(): Float = 0.5f diff --git a/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt index a8199a402ef1..8b3ce0f69742 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt @@ -16,15 +16,18 @@ package com.android.systemui.shade.shared.model +import com.android.systemui.log.table.Diffable +import com.android.systemui.log.table.TableRowLogger + /** Enumerates all known modes of operation of the shade. */ -sealed interface ShadeMode { +sealed class ShadeMode : Diffable<ShadeMode> { /** * The single or "accordion" shade where the QS and notification parts are in two vertically * stacked panels and the user can swipe up and down to expand or collapse between the two * parts. */ - data object Single : ShadeMode + data object Single : ShadeMode() /** * The split shade where, on large screens and unfolded foldables, the QS and notification parts @@ -32,14 +35,18 @@ sealed interface ShadeMode { * * Note: This isn't the only mode where the shade is wide. */ - data object Split : ShadeMode + data object Split : ShadeMode() /** * The dual shade where the QS and notification parts each have their own independently * expandable/collapsible panel on either side of the large screen / unfolded device or sharing * a space on a small screen or folded device. */ - data object Dual : ShadeMode + data object Dual : ShadeMode() + + override fun logDiffs(prevVal: ShadeMode, row: TableRowLogger) { + row.logChange("shadeMode", toString()) + } companion object { @JvmStatic fun dual(): Dual = Dual diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt index d46638fac46c..f5764d59e6ff 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt @@ -418,6 +418,7 @@ object OngoingActivityChipBinder { } is OngoingActivityChipModel.Shown.Timer, is OngoingActivityChipModel.Shown.Text, + is OngoingActivityChipModel.Shown.ShortTimeDelta, is OngoingActivityChipModel.Shown.IconOnly -> { chipView.accessibilityLiveRegion = View.ACCESSIBILITY_LIVE_REGION_NONE } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt index 13f4e51f2ba2..375e02989a3d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt @@ -95,6 +95,10 @@ fun ChipContent(viewModel: OngoingActivityChipModel.Shown, modifier: Modifier = is OngoingActivityChipModel.Shown.ShortTimeDelta -> { // TODO(b/372657935): Implement ShortTimeDelta content in compose. } + + is OngoingActivityChipModel.Shown.IconOnly -> { + throw IllegalStateException("ChipContent should only be used if the chip shows text") + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt index c6d6da2ad9aa..e0c764570132 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt @@ -40,7 +40,7 @@ sealed class OngoingActivityChipModel { } /** This chip should be shown with the given information. */ - abstract class Shown( + sealed class Shown( /** The icon to show on the chip. If null, no icon will be shown. */ open val icon: ChipIcon?, /** What colors to use for the chip. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt index 2d1eccdf1abd..a0a86710b4ba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt @@ -22,6 +22,7 @@ import com.android.internal.widget.MessagingGroup import com.android.internal.widget.MessagingMessage import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback +import com.android.systemui.Flags import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener @@ -144,7 +145,12 @@ internal constructor( ) log { "ViewConfigCoordinator.updateNotificationsOnUiModeChanged()" } traceSection("updateNotifOnUiModeChanged") { - mPipeline?.allNotifs?.forEach { entry -> entry.row?.onUiModeChanged() } + mPipeline?.allNotifs?.forEach { entry -> + entry.row?.onUiModeChanged() + if (Flags.notificationUndoGutsOnConfigChanged()) { + mGutsManager.closeAndUndoGuts() + } + } } } @@ -152,9 +158,15 @@ internal constructor( colorUpdateLogger.logEvent("VCC.updateNotificationsOnDensityOrFontScaleChanged()") mPipeline?.allNotifs?.forEach { entry -> entry.onDensityOrFontScaleChanged() - val exposedGuts = entry.areGutsExposed() - if (exposedGuts) { - mGutsManager.onDensityOrFontScaleChanged(entry) + if (Flags.notificationUndoGutsOnConfigChanged()) { + mGutsManager.closeAndUndoGuts() + } else { + // This property actually gets reset when the guts are re-inflated, so we're never + // actually calling onDensityOrFontScaleChanged below. + val exposedGuts = entry.areGutsExposed() + if (exposedGuts) { + mGutsManager.onDensityOrFontScaleChanged(entry) + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java index b86d1d934269..75d1c7c3d51e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java @@ -287,7 +287,7 @@ public class NotificationGuts extends FrameLayout { * @param save whether the state should be saved * @param force whether the guts should be force-closed regardless of state. */ - private void closeControls(int x, int y, boolean save, boolean force) { + public void closeControls(int x, int y, boolean save, boolean force) { // First try to dismiss any blocking helper. if (getWindowToken() == null) { if (mClosedListener != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index b1e5b22f9b1a..445cd010cd86 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -48,6 +48,7 @@ import com.android.internal.logging.nano.MetricsProto; import com.android.internal.statusbar.IStatusBarService; import com.android.settingslib.notification.ConversationIconFactory; import com.android.systemui.CoreStartable; +import com.android.systemui.Flags; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -223,6 +224,10 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta } public void onDensityOrFontScaleChanged(NotificationEntry entry) { + if (!Flags.notificationUndoGutsOnConfigChanged()) { + Log.wtf(TAG, "onDensityOrFontScaleChanged should not be called if" + + " notificationUndoGutsOnConfigChanged is off"); + } setExposedGuts(entry.getGuts()); bindGuts(entry.getRow()); } @@ -590,7 +595,8 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta } /** - * Closes guts or notification menus that might be visible and saves any changes. + * Closes guts or notification menus that might be visible and saves any changes if applicable + * (see {@link NotificationGuts.GutsContent#shouldBeSavedOnClose}). * * @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed. * @param force true if guts should be closed regardless of state (used for snooze only). @@ -611,6 +617,20 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta } /** + * Closes all guts that might be visible without saving changes. + */ + public void closeAndUndoGuts() { + if (mNotificationGutsExposed != null) { + mNotificationGutsExposed.removeCallbacks(mOpenRunnable); + mNotificationGutsExposed.closeControls( + /* x = */ -1, + /* y = */ -1, + /* save = */ false, + /* force = */ false); + } + } + + /** * Returns the exposed NotificationGuts or null if none are exposed. */ public NotificationGuts getExposedGuts() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java index 99a6f6a59bd0..83897f5bc3a7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java @@ -51,6 +51,7 @@ import com.android.app.animation.Interpolators; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.systemui.Flags; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption; import com.android.systemui.res.R; @@ -86,18 +87,26 @@ public class NotificationSnooze extends LinearLayout private NotificationSwipeActionHelper mSnoozeListener; private StatusBarNotification mSbn; - private View mSnoozeView; - private TextView mSelectedOptionText; + @VisibleForTesting + public View mSnoozeView; + @VisibleForTesting + public TextView mSelectedOptionText; private TextView mUndoButton; - private ImageView mExpandButton; - private View mDivider; - private ViewGroup mSnoozeOptionContainer; - private List<SnoozeOption> mSnoozeOptions; + @VisibleForTesting + public ImageView mExpandButton; + @VisibleForTesting + public View mDivider; + @VisibleForTesting + public ViewGroup mSnoozeOptionContainer; + @VisibleForTesting + public List<SnoozeOption> mSnoozeOptions; private int mCollapsedHeight; private SnoozeOption mDefaultOption; - private SnoozeOption mSelectedOption; + @VisibleForTesting + public SnoozeOption mSelectedOption; private boolean mSnoozing; - private boolean mExpanded; + @VisibleForTesting + public boolean mExpanded; private AnimatorSet mExpandAnimation; private KeyValueListParser mParser; @@ -334,7 +343,8 @@ public class NotificationSnooze extends LinearLayout } } - private void showSnoozeOptions(boolean show) { + @VisibleForTesting + public void showSnoozeOptions(boolean show) { int drawableId = show ? com.android.internal.R.drawable.ic_collapse_notification : com.android.internal.R.drawable.ic_expand_notification; mExpandButton.setImageResource(drawableId); @@ -381,7 +391,8 @@ public class NotificationSnooze extends LinearLayout mExpandAnimation.start(); } - private void setSelected(SnoozeOption option, boolean userAction) { + @VisibleForTesting + public void setSelected(SnoozeOption option, boolean userAction) { if (option != mSelectedOption) { mSelectedOption = option; mSelectedOptionText.setText(option.getConfirmation()); @@ -466,7 +477,12 @@ public class NotificationSnooze extends LinearLayout @Override public boolean handleCloseControls(boolean save, boolean force) { - if (mExpanded && !force) { + if (Flags.notificationUndoGutsOnConfigChanged() && !save) { + // Undo changes and let the guts handle closing the view + mSelectedOption = null; + showSnoozeOptions(false); + return false; + } else if (mExpanded && !force) { // Collapse expanded state on outside touch showSnoozeOptions(false); return true; diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt index 9795cda97f37..eecea9228ea3 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt @@ -27,6 +27,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawingPadding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons @@ -82,18 +83,29 @@ fun TutorialSelectionScreen( } ), ) { - val padding = if (hasCompactWindowSize()) 24.dp else 60.dp + val isCompactWindow = hasCompactWindowSize() + val padding = if (isCompactWindow) 24.dp else 60.dp val configuration = LocalConfiguration.current when (configuration.orientation) { Configuration.ORIENTATION_LANDSCAPE -> { - HorizontalSelectionButtons( - onBackTutorialClicked = onBackTutorialClicked, - onHomeTutorialClicked = onHomeTutorialClicked, - onRecentAppsTutorialClicked = onRecentAppsTutorialClicked, - onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked, - modifier = Modifier.weight(1f).padding(padding), - lastSelectedScreen, - ) + if (isCompactWindow) + HorizontalCompactSelectionButtons( + onBackTutorialClicked = onBackTutorialClicked, + onHomeTutorialClicked = onHomeTutorialClicked, + onRecentAppsTutorialClicked = onRecentAppsTutorialClicked, + onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked, + lastSelectedScreen, + modifier = Modifier.weight(1f).padding(padding), + ) + else + HorizontalSelectionButtons( + onBackTutorialClicked = onBackTutorialClicked, + onHomeTutorialClicked = onHomeTutorialClicked, + onRecentAppsTutorialClicked = onRecentAppsTutorialClicked, + onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked, + lastSelectedScreen, + modifier = Modifier.weight(1f).padding(padding), + ) } else -> { VerticalSelectionButtons( @@ -101,8 +113,8 @@ fun TutorialSelectionScreen( onHomeTutorialClicked = onHomeTutorialClicked, onRecentAppsTutorialClicked = onRecentAppsTutorialClicked, onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked, - modifier = Modifier.weight(1f).padding(padding), lastSelectedScreen, + modifier = Modifier.weight(1f).padding(padding), ) } } @@ -120,11 +132,99 @@ private fun HorizontalSelectionButtons( onHomeTutorialClicked: () -> Unit, onRecentAppsTutorialClicked: () -> Unit, onSwitchAppsTutorialClicked: () -> Unit, + lastSelectedScreen: Screen, modifier: Modifier = Modifier, +) { + Column(modifier = modifier) { + TwoByTwoTutorialButtons( + onBackTutorialClicked, + onHomeTutorialClicked, + onRecentAppsTutorialClicked, + onSwitchAppsTutorialClicked, + lastSelectedScreen, + modifier = Modifier.weight(1f).fillMaxSize(), + ) + } +} + +@Composable +private fun TwoByTwoTutorialButtons( + onBackTutorialClicked: () -> Unit, + onHomeTutorialClicked: () -> Unit, + onRecentAppsTutorialClicked: () -> Unit, + onSwitchAppsTutorialClicked: () -> Unit, lastSelectedScreen: Screen, + modifier: Modifier = Modifier, +) { + val homeFocusRequester = remember { FocusRequester() } + val backFocusRequester = remember { FocusRequester() } + val recentAppsFocusRequester = remember { FocusRequester() } + val switchAppsFocusRequester = remember { FocusRequester() } + LaunchedEffect(Unit) { + when (lastSelectedScreen) { + Screen.HOME_GESTURE -> homeFocusRequester.requestFocus() + Screen.BACK_GESTURE -> backFocusRequester.requestFocus() + Screen.RECENT_APPS_GESTURE -> recentAppsFocusRequester.requestFocus() + Screen.SWITCH_APPS_GESTURE -> switchAppsFocusRequester.requestFocus() + else -> {} // No-Op. + } + } + Column { + Row(Modifier.weight(1f)) { + TutorialButton( + text = stringResource(R.string.touchpad_tutorial_home_gesture_button), + icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_home_icon), + iconColor = MaterialTheme.colorScheme.onPrimary, + onClick = onHomeTutorialClicked, + backgroundColor = MaterialTheme.colorScheme.primary, + modifier = modifier.focusRequester(homeFocusRequester).focusable().fillMaxSize(), + ) + Spacer(modifier = Modifier.size(16.dp)) + TutorialButton( + text = stringResource(R.string.touchpad_tutorial_back_gesture_button), + icon = Icons.AutoMirrored.Outlined.ArrowBack, + iconColor = MaterialTheme.colorScheme.onTertiary, + onClick = onBackTutorialClicked, + backgroundColor = MaterialTheme.colorScheme.tertiary, + modifier = modifier.focusRequester(backFocusRequester).focusable().fillMaxSize(), + ) + } + Spacer(modifier = Modifier.size(16.dp)) + Row(Modifier.weight(1f)) { + TutorialButton( + text = stringResource(R.string.touchpad_tutorial_recent_apps_gesture_button), + icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_recents_icon), + iconColor = MaterialTheme.colorScheme.onSecondary, + onClick = onRecentAppsTutorialClicked, + backgroundColor = MaterialTheme.colorScheme.secondary, + modifier = + modifier.focusRequester(recentAppsFocusRequester).focusable().fillMaxSize(), + ) + Spacer(modifier = Modifier.size(16.dp)) + TutorialButton( + text = stringResource(R.string.touchpad_tutorial_switch_apps_gesture_button), + icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_apps_icon), + iconColor = MaterialTheme.colorScheme.primary, + onClick = onSwitchAppsTutorialClicked, + backgroundColor = MaterialTheme.colorScheme.onPrimary, + modifier = + modifier.focusRequester(switchAppsFocusRequester).focusable().fillMaxSize(), + ) + } + } +} + +@Composable +private fun HorizontalCompactSelectionButtons( + onBackTutorialClicked: () -> Unit, + onHomeTutorialClicked: () -> Unit, + onRecentAppsTutorialClicked: () -> Unit, + onSwitchAppsTutorialClicked: () -> Unit, + lastSelectedScreen: Screen, + modifier: Modifier = Modifier, ) { Row( - horizontalArrangement = Arrangement.spacedBy(20.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically, modifier = modifier, ) { @@ -133,8 +233,8 @@ private fun HorizontalSelectionButtons( onHomeTutorialClicked, onRecentAppsTutorialClicked, onSwitchAppsTutorialClicked, - modifier = Modifier.weight(1f).fillMaxSize(), lastSelectedScreen, + modifier = Modifier.weight(1f).fillMaxSize(), ) } } @@ -145,8 +245,8 @@ private fun VerticalSelectionButtons( onHomeTutorialClicked: () -> Unit, onRecentAppsTutorialClicked: () -> Unit, onSwitchAppsTutorialClicked: () -> Unit, - modifier: Modifier = Modifier, lastSelectedScreen: Screen, + modifier: Modifier = Modifier, ) { Column( verticalArrangement = Arrangement.spacedBy(16.dp), @@ -158,8 +258,8 @@ private fun VerticalSelectionButtons( onHomeTutorialClicked, onRecentAppsTutorialClicked, onSwitchAppsTutorialClicked, - modifier = Modifier.weight(1f).fillMaxSize(), lastSelectedScreen, + modifier = Modifier.weight(1f).fillMaxSize(), ) } } @@ -170,8 +270,8 @@ private fun FourTutorialButtons( onHomeTutorialClicked: () -> Unit, onRecentAppsTutorialClicked: () -> Unit, onSwitchAppsTutorialClicked: () -> Unit, - modifier: Modifier = Modifier, lastSelectedScreen: Screen, + modifier: Modifier = Modifier, ) { val homeFocusRequester = remember { FocusRequester() } val backFocusRequester = remember { FocusRequester() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt index 2bd104dd375d..48b801cb06be 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt @@ -20,6 +20,7 @@ import com.android.systemui.authentication.data.repository.authenticationReposit import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.log.table.logcatTableLogBuffer import com.android.systemui.user.domain.interactor.selectedUserInteractor val Kosmos.authenticationInteractor by @@ -29,5 +30,6 @@ val Kosmos.authenticationInteractor by backgroundDispatcher = testDispatcher, repository = authenticationRepository, selectedUserInteractor = selectedUserInteractor, + tableLogBuffer = logcatTableLogBuffer(this, "sceneFrameworkTableLogBuffer"), ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/BatteryRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/BatteryRepositoryKosmos.kt new file mode 100644 index 000000000000..edfe8ecd0775 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/BatteryRepositoryKosmos.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 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.systemui.common.data.repository + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.batteryRepository: BatteryRepository by Kosmos.Fixture { FakeBatteryRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakeBatteryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakeBatteryRepository.kt new file mode 100644 index 000000000000..ac94335b42c3 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakeBatteryRepository.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 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.systemui.common.data.repository + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +class FakeBatteryRepository : BatteryRepository { + private val _isDevicePluggedIn = MutableStateFlow(false) + + override val isDevicePluggedIn: Flow<Boolean> = _isDevicePluggedIn.asStateFlow() + + fun setDevicePluggedIn(isPluggedIn: Boolean) { + _isDevicePluggedIn.value = isPluggedIn + } +} + +val BatteryRepository.fake + get() = this as FakeBatteryRepository diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/BatteryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/BatteryInteractorKosmos.kt new file mode 100644 index 000000000000..2153955f3cc1 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/BatteryInteractorKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 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.systemui.common.domain.interactor + +import com.android.systemui.common.data.repository.batteryRepository +import com.android.systemui.kosmos.Kosmos + +var Kosmos.batteryInteractor by Kosmos.Fixture { BatteryInteractor(batteryRepository) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt index 163625747d85..603160dea715 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt @@ -25,7 +25,8 @@ import kotlinx.coroutines.flow.map /** Fake implementation of [CommunalPrefsRepository] */ class FakeCommunalPrefsRepository : CommunalPrefsRepository { private val _isCtaDismissed = MutableStateFlow<Set<UserInfo>>(emptySet()) - private val _isHubOnboardingismissed = MutableStateFlow<Set<UserInfo>>(emptySet()) + private val _isHubOnboardingDismissed = MutableStateFlow<Set<UserInfo>>(emptySet()) + private val _isDreamButtonTooltipDismissed = MutableStateFlow<Set<UserInfo>>(emptySet()) override fun isCtaDismissed(user: UserInfo): Flow<Boolean> = _isCtaDismissed.map { it.contains(user) } @@ -35,10 +36,18 @@ class FakeCommunalPrefsRepository : CommunalPrefsRepository { } override fun isHubOnboardingDismissed(user: UserInfo): Flow<Boolean> = - _isHubOnboardingismissed.map { it.contains(user) } + _isHubOnboardingDismissed.map { it.contains(user) } override suspend fun setHubOnboardingDismissed(user: UserInfo) { - _isHubOnboardingismissed.value = - _isHubOnboardingismissed.value.toMutableSet().apply { add(user) } + _isHubOnboardingDismissed.value = + _isHubOnboardingDismissed.value.toMutableSet().apply { add(user) } + } + + override fun isDreamButtonTooltipDismissed(user: UserInfo): Flow<Boolean> = + _isDreamButtonTooltipDismissed.map { it.contains(user) } + + override suspend fun setDreamButtonTooltipDismissed(user: UserInfo) { + _isDreamButtonTooltipDismissed.value = + _isDreamButtonTooltipDismissed.value.toMutableSet().apply { add(user) } } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt index 89aad4be7cc0..b0a6de1f931a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt @@ -19,10 +19,13 @@ package com.android.systemui.communal.domain.interactor import android.content.testableContext import android.os.userManager import com.android.systemui.broadcast.broadcastDispatcher +import com.android.systemui.common.domain.interactor.batteryInteractor import com.android.systemui.communal.data.repository.communalMediaRepository import com.android.systemui.communal.data.repository.communalSmartspaceRepository import com.android.systemui.communal.data.repository.communalWidgetRepository +import com.android.systemui.communal.posturing.domain.interactor.posturingInteractor import com.android.systemui.communal.widgets.EditWidgetsActivityStarter +import com.android.systemui.dock.dockManager import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository @@ -64,6 +67,9 @@ val Kosmos.communalInteractor by Fixture { logBuffer = logcatLogBuffer("CommunalInteractor"), tableLogBuffer = mock(), managedProfileController = fakeManagedProfileController, + batteryInteractor = batteryInteractor, + dockManager = dockManager, + posturingInteractor = posturingInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt index c2d2392186b7..43d3eb7b857a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt @@ -18,6 +18,7 @@ package com.android.systemui.communal.ui.viewmodel import android.service.dream.dreamManager import com.android.internal.logging.uiEventLogger +import com.android.systemui.communal.domain.interactor.communalPrefsInteractor import com.android.systemui.communal.domain.interactor.communalSettingsInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testDispatcher @@ -29,6 +30,7 @@ val Kosmos.communalToDreamButtonViewModel by CommunalToDreamButtonViewModel( backgroundContext = testDispatcher, batteryController = batteryController, + prefsInteractor = communalPrefsInteractor, settingsInteractor = communalSettingsInteractor, activityStarter = activityStarter, dreamManager = dreamManager, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt index 1d3fd300da06..c927b5563bba 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt @@ -22,6 +22,7 @@ import com.android.systemui.deviceentry.data.repository.deviceEntryRepository import com.android.systemui.keyguard.dismissCallbackRegistry import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.log.table.logcatTableLogBuffer import com.android.systemui.scene.domain.interactor.sceneBackInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor @@ -36,5 +37,6 @@ val Kosmos.deviceEntryInteractor by alternateBouncerInteractor = alternateBouncerInteractor, dismissCallbackRegistry = dismissCallbackRegistry, sceneBackInteractor = sceneBackInteractor, + tableLogBuffer = logcatTableLogBuffer(this, "sceneFrameworkTableLogBuffer"), ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt index e4c7df64fdc6..9e36428d119d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt @@ -25,6 +25,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn +import com.android.systemui.log.table.logcatTableLogBuffer import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository @@ -40,6 +41,7 @@ val Kosmos.deviceUnlockedInteractor by Fixture { systemPropertiesHelper = fakeSystemPropertiesHelper, userAwareSecureSettingsRepository = userAwareSecureSettingsRepository, keyguardInteractor = keyguardInteractor, + tableLogBuffer = logcatTableLogBuffer(this, "sceneFrameworkTableLogBuffer"), ) .apply { activateIn(testScope) } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt index b07de16be567..ff7a06c5087e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt @@ -16,6 +16,8 @@ package com.android.systemui.keyguard.domain.interactor +import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.communal.domain.interactor.communalSceneInteractor import com.android.systemui.communal.domain.interactor.communalSettingsInteractor import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository import com.android.systemui.kosmos.Kosmos @@ -41,5 +43,7 @@ var Kosmos.fromLockscreenTransitionInteractor by communalSettingsInteractor = communalSettingsInteractor, swipeToDismissInteractor = swipeToDismissInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, + communalInteractor = communalInteractor, + communalSceneInteractor = communalSceneInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorKosmos.kt index e46ede65bfb6..e9ba42547883 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorKosmos.kt @@ -18,6 +18,7 @@ package com.android.systemui.scene.domain.interactor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.log.table.logcatTableLogBuffer import com.android.systemui.scene.sceneContainerConfig import com.android.systemui.scene.shared.logger.sceneLogger @@ -25,5 +26,6 @@ val Kosmos.sceneBackInteractor by Fixture { SceneBackInteractor( logger = sceneLogger, sceneContainerConfig = sceneContainerConfig, + tableLogBuffer = logcatTableLogBuffer(this, "sceneFrameworkTableLogBuffer"), ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt index d105326ec3d0..65bfafbfa9b0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt @@ -36,6 +36,7 @@ import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.testScope +import com.android.systemui.log.table.logcatTableLogBuffer import com.android.systemui.model.sysUiState import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.scene.domain.interactor.disabledContentInteractor @@ -89,5 +90,6 @@ val Kosmos.sceneContainerStartable by Fixture { disabledContentInteractor = disabledContentInteractor, activityTransitionAnimator = activityTransitionAnimator, shadeModeInteractor = shadeModeInteractor, + tableLogBuffer = logcatTableLogBuffer(this, "sceneFrameworkTableLogBuffer"), ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt index a4631f17cb37..2ba9c8094aac 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt @@ -21,6 +21,7 @@ import android.provider.Settings import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.log.table.logcatTableLogBuffer import com.android.systemui.res.R import com.android.systemui.shade.data.repository.fakeShadeRepository import com.android.systemui.shade.data.repository.shadeRepository @@ -31,6 +32,7 @@ val Kosmos.shadeModeInteractor by Fixture { applicationScope = applicationCoroutineScope, repository = shadeRepository, secureSettingsRepository = fakeSecureSettingsRepository, + tableLogBuffer = logcatTableLogBuffer(this, "sceneFrameworkTableLogBuffer"), ) } diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java index 3de84f17b583..d2db8f74cd05 100644 --- a/services/core/java/com/android/server/DockObserver.java +++ b/services/core/java/com/android/server/DockObserver.java @@ -27,7 +27,6 @@ import android.media.RingtoneManager; import android.net.Uri; import android.os.Binder; import android.os.Handler; -import android.os.Message; import android.os.PowerManager; import android.os.SystemClock; import android.os.UEventObserver; @@ -37,6 +36,7 @@ import android.util.Pair; import android.util.Slog; import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; import com.android.internal.util.FrameworkStatsLog; @@ -57,8 +57,6 @@ import java.util.Map; final class DockObserver extends SystemService { private static final String TAG = "DockObserver"; - private static final int MSG_DOCK_STATE_CHANGED = 0; - private final PowerManager mPowerManager; private final PowerManager.WakeLock mWakeLock; @@ -66,11 +64,16 @@ final class DockObserver extends SystemService { private boolean mSystemReady; + @GuardedBy("mLock") private int mActualDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED; + @GuardedBy("mLock") private int mReportedDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED; + + @GuardedBy("mLock") private int mPreviousDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED; + @GuardedBy("mLock") private boolean mUpdatesStopped; private final boolean mKeepDreamingWhenUnplugging; @@ -182,18 +185,24 @@ final class DockObserver extends SystemService { ExtconInfo.EXTCON_DOCK }); - if (!infos.isEmpty()) { - ExtconInfo info = infos.get(0); - Slog.i(TAG, "Found extcon info devPath: " + info.getDevicePath() - + ", statePath: " + info.getStatePath()); - - // set initial status - setDockStateFromProviderLocked(ExtconStateProvider.fromFile(info.getStatePath())); - mPreviousDockState = mActualDockState; - - mExtconUEventObserver.startObserving(info); - } else { - Slog.i(TAG, "No extcon dock device found in this kernel."); + synchronized (mLock) { + if (!infos.isEmpty()) { + ExtconInfo info = infos.get(0); + Slog.i( + TAG, + "Found extcon info devPath: " + + info.getDevicePath() + + ", statePath: " + + info.getStatePath()); + + // set initial status + setDockStateFromProviderLocked(ExtconStateProvider.fromFile(info.getStatePath())); + mPreviousDockState = mActualDockState; + + mExtconUEventObserver.startObserving(info); + } else { + Slog.i(TAG, "No extcon dock device found in this kernel."); + } } mDockObserverLocalService = new DockObserverLocalService(); @@ -223,13 +232,15 @@ final class DockObserver extends SystemService { } } + @GuardedBy("mLock") private void updateIfDockedLocked() { // don't bother broadcasting undocked here if (mReportedDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) { - updateLocked(); + postWakefulDockStateChange(); } } + @GuardedBy("mLock") private void setActualDockStateLocked(int newState) { mActualDockState = newState; if (!mUpdatesStopped) { @@ -237,6 +248,7 @@ final class DockObserver extends SystemService { } } + @GuardedBy("mLock") private void setDockStateLocked(int newState) { if (newState != mReportedDockState) { mReportedDockState = newState; @@ -246,10 +258,12 @@ final class DockObserver extends SystemService { if (mSystemReady) { // Wake up immediately when docked or undocked unless prohibited from doing so. if (allowWakeFromDock()) { - mPowerManager.wakeUp(SystemClock.uptimeMillis(), + mPowerManager.wakeUp( + SystemClock.uptimeMillis(), + PowerManager.WAKE_REASON_DOCK, "android.server:DOCK"); } - updateLocked(); + postWakefulDockStateChange(); } } } @@ -263,9 +277,8 @@ final class DockObserver extends SystemService { Settings.Global.THEATER_MODE_ON, 0) == 0); } - private void updateLocked() { - mWakeLock.acquire(); - mHandler.sendEmptyMessage(MSG_DOCK_STATE_CHANGED); + private void postWakefulDockStateChange() { + mHandler.post(mWakeLock.wrap(this::handleDockStateChange)); } private void handleDockStateChange() { @@ -348,17 +361,7 @@ final class DockObserver extends SystemService { } } - private final Handler mHandler = new Handler(true /*async*/) { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_DOCK_STATE_CHANGED: - handleDockStateChange(); - mWakeLock.release(); - break; - } - } - }; + private final Handler mHandler = new Handler(true /*async*/); private int getDockedStateExtraValue(ExtconStateProvider state) { for (ExtconStateConfig config : mExtconStateConfigs) { @@ -386,6 +389,7 @@ final class DockObserver extends SystemService { } } + @GuardedBy("mLock") private void setDockStateFromProviderLocked(ExtconStateProvider provider) { int state = Intent.EXTRA_DOCK_STATE_UNDOCKED; if ("1".equals(provider.getValue("DOCK"))) { diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index d335529a006a..ce526e510053 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -572,6 +572,7 @@ public class CachedAppOptimizer { public long mTotalAnonMemFreedKBs; public long mSumOrigAnonRss; public double mMaxCompactEfficiency; + public double mMaxSwapEfficiency; // Cpu time public long mTotalCpuTimeMillis; @@ -586,6 +587,10 @@ public class CachedAppOptimizer { if (compactEfficiency > mMaxCompactEfficiency) { mMaxCompactEfficiency = compactEfficiency; } + final double swapEfficiency = anonRssSaved / (double) origAnonRss; + if (swapEfficiency > mMaxSwapEfficiency) { + mMaxSwapEfficiency = swapEfficiency; + } mTotalDeltaAnonRssKBs += anonRssSaved; mTotalZramConsumedKBs += zramConsumed; mTotalAnonMemFreedKBs += memFreed; @@ -628,7 +633,11 @@ public class CachedAppOptimizer { pw.println(" -----Memory Stats----"); pw.println(" Total Delta Anon RSS (KB) : " + mTotalDeltaAnonRssKBs); pw.println(" Total Physical ZRAM Consumed (KB): " + mTotalZramConsumedKBs); + // Anon Mem Freed = Delta Anon RSS - ZRAM Consumed pw.println(" Total Anon Memory Freed (KB): " + mTotalAnonMemFreedKBs); + pw.println(" Avg Swap Efficiency (KB) (Delta Anon RSS/Orig Anon RSS): " + + (mTotalDeltaAnonRssKBs / (double) mSumOrigAnonRss)); + pw.println(" Max Swap Efficiency: " + mMaxSwapEfficiency); // This tells us how much anon memory we were able to free thanks to running // compaction pw.println(" Avg Compaction Efficiency (Anon Freed/Anon RSS): " @@ -808,8 +817,9 @@ public class CachedAppOptimizer { pw.println(" Tracking last compaction stats for " + mLastCompactionStats.size() + " processes."); pw.println("Last Compaction per process stats:"); - pw.println(" (ProcessName,Source,DeltaAnonRssKBs,ZramConsumedKBs,AnonMemFreedKBs," - + "CompactEfficiency,CompactCost(ms/MB),procState,oomAdj,oomAdjReason)"); + pw.println(" (ProcessName,Source,DeltaAnonRssKBs,ZramConsumedKBs,AnonMemFreedKBs" + + ",SwapEfficiency,CompactEfficiency,CompactCost(ms/MB),procState,oomAdj," + + "oomAdjReason)"); for (Map.Entry<Integer, SingleCompactionStats> entry : mLastCompactionStats.entrySet()) { SingleCompactionStats stats = entry.getValue(); @@ -818,7 +828,8 @@ public class CachedAppOptimizer { pw.println(); pw.println("Last 20 Compactions Stats:"); pw.println(" (ProcessName,Source,DeltaAnonRssKBs,ZramConsumedKBs,AnonMemFreedKBs," - + "CompactEfficiency,CompactCost(ms/MB),procState,oomAdj,oomAdjReason)"); + + "SwapEfficiency,CompactEfficiency,CompactCost(ms/MB),procState,oomAdj," + + "oomAdjReason)"); for (SingleCompactionStats stats : mCompactionStatsHistory) { stats.dump(pw); } @@ -1779,6 +1790,8 @@ public class CachedAppOptimizer { double getCompactEfficiency() { return mAnonMemFreedKBs / (double) mOrigAnonRss; } + double getSwapEfficiency() { return mDeltaAnonRssKBs / (double) mOrigAnonRss; } + double getCompactCost() { // mCpuTimeMillis / (anonMemFreedKBs/1024) and metric is in (ms/MB) return mCpuTimeMillis / (double) mAnonMemFreedKBs * 1024; @@ -1791,7 +1804,8 @@ public class CachedAppOptimizer { @NeverCompile void dump(PrintWriter pw) { pw.println(" (" + mProcessName + "," + mSourceType.name() + "," + mDeltaAnonRssKBs - + "," + mZramConsumedKBs + "," + mAnonMemFreedKBs + "," + getCompactEfficiency() + + "," + mZramConsumedKBs + "," + mAnonMemFreedKBs + "," + + getSwapEfficiency() + "," + getCompactEfficiency() + "," + getCompactCost() + "," + mProcState + "," + mOomAdj + "," + OomAdjuster.oomAdjReasonToString(mOomAdjReason) + ")"); } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 5f118cb06d74..f2830090e7db 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -837,6 +837,7 @@ public class AudioService extends IAudioService.Stub private final Executor mAudioServerLifecycleExecutor; private long mSysPropListenerNativeHandle; + private CacheWatcher mCacheWatcher; private final List<Future> mScheduledPermissionTasks = new ArrayList(); private IMediaProjectionManager mProjectionService; // to validate projection token @@ -11093,31 +11094,26 @@ public class AudioService extends IAudioService.Stub }, getAudioPermissionsDelay(), TimeUnit.MILLISECONDS)); } }; - mSysPropListenerNativeHandle = mAudioSystem.listenForSystemPropertyChange( - PermissionManager.CACHE_KEY_PACKAGE_INFO_NOTIFY, - task); + if (PropertyInvalidatedCache.separatePermissionNotificationsEnabled()) { + mCacheWatcher = new CacheWatcher(task); + mCacheWatcher.start(); + } else { + mSysPropListenerNativeHandle = mAudioSystem.listenForSystemPropertyChange( + PermissionManager.CACHE_KEY_PACKAGE_INFO_NOTIFY, + task); + } } else { mAudioSystem.listenForSystemPropertyChange( PermissionManager.CACHE_KEY_PACKAGE_INFO_NOTIFY, () -> mAudioServerLifecycleExecutor.execute( mPermissionProvider::onPermissionStateChanged)); } - - if (PropertyInvalidatedCache.separatePermissionNotificationsEnabled()) { - new PackageInfoTransducer().start(); - } } /** - * A transducer that converts high-speed changes in the CACHE_KEY_PACKAGE_INFO_CACHE - * PropertyInvalidatedCache into low-speed changes in the CACHE_KEY_PACKAGE_INFO_NOTIFY system - * property. This operates on the popcorn principle: changes in the source are done when the - * source has been quiet for the soak interval. - * - * TODO(b/381097912) This is a temporary measure to support migration away from sysprop - * sniffing. It should be cleaned up. + * Listens for CACHE_KEY_PACKAGE_INFO_CACHE invalidations to trigger permission syncing */ - private static class PackageInfoTransducer extends Thread { + private static class CacheWatcher extends Thread { // The run/stop signal. private final AtomicBoolean mRunning = new AtomicBoolean(false); @@ -11125,81 +11121,33 @@ public class AudioService extends IAudioService.Stub // The source of change information. private final PropertyInvalidatedCache.NonceWatcher mWatcher; - // The handler for scheduling delayed reactions to changes. - private final Handler mHandler; + // Task to trigger when cache changes + private final Runnable mTask; - // How long to soak changes: 50ms is the legacy choice. - private final static long SOAK_TIME_MS = 50; - - // The ubiquitous lock. - private final Object mLock = new Object(); - - // If positive, this is the soak expiration time. - @GuardedBy("mLock") - private long mSoakDeadlineMs = -1; - - // A source of unique long values. - @GuardedBy("mLock") - private long mToken = 0; - - PackageInfoTransducer() { - mWatcher = PropertyInvalidatedCache - .getNonceWatcher(PermissionManager.CACHE_KEY_PACKAGE_INFO_CACHE); - mHandler = new Handler(BackgroundThread.getHandler().getLooper()) { - @Override - public void handleMessage(Message msg) { - PackageInfoTransducer.this.handleMessage(msg); - }}; + public CacheWatcher(Runnable r) { + mWatcher = PropertyInvalidatedCache.getNonceWatcher( + PermissionManager.CACHE_KEY_PACKAGE_INFO_CACHE); + mTask = r; } public void run() { mRunning.set(true); while (mRunning.get()) { + doCheck(); try { - final int changes = mWatcher.waitForChange(); - if (changes == 0 || !mRunning.get()) { - continue; - } + mWatcher.waitForChange(); } catch (InterruptedException e) { + Log.wtf(TAG, "Unexpected Interrupt", e); // We don't know why the exception occurred but keep running until told to // stop. continue; } - trigger(); } } - @GuardedBy("mLock") - private void updateLocked() { - String n = Long.toString(mToken++); - SystemPropertySetter.setWithRetry(PermissionManager.CACHE_KEY_PACKAGE_INFO_NOTIFY, n); - } - - private void trigger() { - synchronized (mLock) { - boolean alreadyQueued = mSoakDeadlineMs >= 0; - final long nowMs = SystemClock.uptimeMillis(); - mSoakDeadlineMs = nowMs + SOAK_TIME_MS; - if (!alreadyQueued) { - mHandler.sendEmptyMessageAtTime(0, mSoakDeadlineMs); - updateLocked(); - } - } - } - - private void handleMessage(Message msg) { - synchronized (mLock) { - if (mSoakDeadlineMs < 0) { - return; // ??? - } - final long nowMs = SystemClock.uptimeMillis(); - if (mSoakDeadlineMs > nowMs) { - mSoakDeadlineMs = nowMs + SOAK_TIME_MS; - mHandler.sendEmptyMessageAtTime(0, mSoakDeadlineMs); - return; - } - mSoakDeadlineMs = -1; - updateLocked(); + public synchronized void doCheck() { + if (mWatcher.isChanged()) { + mTask.run(); } } @@ -15376,7 +15324,11 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#permissionUpdateBarrier() */ public void permissionUpdateBarrier() { if (!audioserverPermissions()) return; - mAudioSystem.triggerSystemPropertyUpdate(mSysPropListenerNativeHandle); + if (PropertyInvalidatedCache.separatePermissionNotificationsEnabled()) { + mCacheWatcher.doCheck(); + } else { + mAudioSystem.triggerSystemPropertyUpdate(mSysPropListenerNativeHandle); + } List<Future> snapshot; synchronized (mScheduledPermissionTasks) { snapshot = List.copyOf(mScheduledPermissionTasks); diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java index ab1778a1a32e..15c0789d777e 100644 --- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java @@ -295,9 +295,14 @@ class AppCompatAspectRatioPolicy { // {@link ActivityRecord#shouldCreateAppCompatDisplayInsets()} will be false for // both activities that are naturally resizeable and activities that have been // forced resizeable. + // Camera compat mode is an exception to this, where the activity is letterboxed + // to an aspect ratio commonly found on phones, e.g. 16:9, to avoid issues like + // stretching of the camera preview. || (Flags.ignoreAspectRatioRestrictionsForResizeableFreeformActivities() && task.getWindowingMode() == WINDOWING_MODE_FREEFORM - && !mActivityRecord.shouldCreateAppCompatDisplayInsets())) { + && !mActivityRecord.shouldCreateAppCompatDisplayInsets() + && !AppCompatCameraPolicy.shouldCameraCompatControlAspectRatio( + mActivityRecord))) { return false; } diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index e1f3f0ef5615..bf4595c815bd 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -94,8 +94,6 @@ namespace input_flags = com::android::input::flags; namespace android { -static const bool ENABLE_INPUT_FILTER_RUST = input_flags::enable_input_filter_rust_impl(); - // The exponent used to calculate the pointer speed scaling factor. // The scaling factor is calculated as 2 ^ (speed * exponent), // where the speed ranges from -7 to + 7 and is supplied by the user. @@ -3248,27 +3246,21 @@ static void nativeSetStylusPointerIconEnabled(JNIEnv* env, jobject nativeImplObj static void nativeSetAccessibilityBounceKeysThreshold(JNIEnv* env, jobject nativeImplObj, jint thresholdTimeMs) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - if (ENABLE_INPUT_FILTER_RUST) { - im->getInputManager()->getInputFilter().setAccessibilityBounceKeysThreshold( - static_cast<nsecs_t>(thresholdTimeMs) * 1000000); - } + im->getInputManager()->getInputFilter().setAccessibilityBounceKeysThreshold( + static_cast<nsecs_t>(thresholdTimeMs) * 1000000); } static void nativeSetAccessibilitySlowKeysThreshold(JNIEnv* env, jobject nativeImplObj, jint thresholdTimeMs) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - if (ENABLE_INPUT_FILTER_RUST) { - im->getInputManager()->getInputFilter().setAccessibilitySlowKeysThreshold( - static_cast<nsecs_t>(thresholdTimeMs) * 1000000); - } + im->getInputManager()->getInputFilter().setAccessibilitySlowKeysThreshold( + static_cast<nsecs_t>(thresholdTimeMs) * 1000000); } static void nativeSetAccessibilityStickyKeysEnabled(JNIEnv* env, jobject nativeImplObj, jboolean enabled) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - if (ENABLE_INPUT_FILTER_RUST) { - im->getInputManager()->getInputFilter().setAccessibilityStickyKeysEnabled(enabled); - } + im->getInputManager()->getInputFilter().setAccessibilityStickyKeysEnabled(enabled); } static void nativeSetInputMethodConnectionIsActive(JNIEnv* env, jobject nativeImplObj, diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 7af4ede05363..c3aa2894997d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -4953,7 +4953,8 @@ public class SizeCompatTests extends WindowTestsBase { } @Test - @EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) + @EnableFlags({Flags.FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES, + Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING}) public void testCameraCompatAspectRatioAppliedForFixedOrientationCameraActivities() { // Needed to create camera compat policy in DisplayContent. allowDesktopMode(); @@ -4965,7 +4966,8 @@ public class SizeCompatTests extends WindowTestsBase { setupCameraCompatAspectRatio(cameraCompatAspectRatio, display); // Create task on test display. - final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build(); + final Task task = new TaskBuilder(mSupervisor).setDisplay(display) + .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); // Create fixed portrait activity. final ActivityRecord fixedOrientationActivity = new ActivityBuilder(mAtm) @@ -4978,7 +4980,8 @@ public class SizeCompatTests extends WindowTestsBase { } @Test - @EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) + @EnableFlags({Flags.FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES, + Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING}) public void testCameraCompatAspectRatioForFixedOrientationCameraActivitiesPortraitWindow() { // Needed to create camera compat policy in DisplayContent. allowDesktopMode(); @@ -4990,7 +4993,8 @@ public class SizeCompatTests extends WindowTestsBase { setupCameraCompatAspectRatio(cameraCompatAspectRatio, display); // Create task on test display. - final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build(); + final Task task = new TaskBuilder(mSupervisor).setDisplay(display) + .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); // Create fixed portrait activity. final ActivityRecord fixedOrientationActivity = new ActivityBuilder(mAtm) @@ -5003,7 +5007,8 @@ public class SizeCompatTests extends WindowTestsBase { } @Test - @EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) + @EnableFlags({Flags.FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES, + Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING}) public void testCameraCompatAspectRatioAppliedInsteadOfDefaultAspectRatio() { // Needed to create camera compat policy in DisplayContent. allowDesktopMode(); @@ -5015,7 +5020,8 @@ public class SizeCompatTests extends WindowTestsBase { setupCameraCompatAspectRatio(cameraCompatAspectRatio, display); // Create task on test display. - final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build(); + final Task task = new TaskBuilder(mSupervisor).setDisplay(display) + .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); // App's target min aspect ratio - this should not be used, as camera controls aspect ratio. final float targetMinAspectRatio = 4.0f; @@ -5032,7 +5038,8 @@ public class SizeCompatTests extends WindowTestsBase { } @Test - @EnableFlags(Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) + @EnableFlags({Flags.FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES, + Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING}) public void testCameraCompatAspectRatio_defaultAspectRatioAppliedWhenGreater() { // Needed to create camera compat policy in DisplayContent. allowDesktopMode(); @@ -5044,7 +5051,8 @@ public class SizeCompatTests extends WindowTestsBase { setupCameraCompatAspectRatio(cameraCompatAspectRatio, display); // Create task on test display. - final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build(); + final Task task = new TaskBuilder(mSupervisor).setDisplay(display) + .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); // App's target min aspect ratio bigger than camera compat aspect ratio - use that instead. final float targetMinAspectRatio = 6.0f; diff --git a/tests/CompanionDeviceMultiDeviceTests/host/Android.bp b/tests/CompanionDeviceMultiDeviceTests/host/Android.bp index a0e047759dab..1fb18a6bb391 100644 --- a/tests/CompanionDeviceMultiDeviceTests/host/Android.bp +++ b/tests/CompanionDeviceMultiDeviceTests/host/Android.bp @@ -39,13 +39,4 @@ python_test_host { device_common_data: [ ":cdm_snippet_legacy", ], - version: { - py2: { - enabled: false, - }, - py3: { - enabled: true, - embedded_launcher: true, - }, - }, } diff --git a/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt b/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt index 1c2a0538e552..c2f9adf84ccd 100644 --- a/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt +++ b/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt @@ -50,10 +50,7 @@ import org.mockito.junit.MockitoJUnitRunner */ @Presubmit @RunWith(MockitoJUnitRunner::class) -@EnableFlags( - com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG, - com.android.input.flags.Flags.FLAG_ENABLE_INPUT_FILTER_RUST_IMPL, -) +@EnableFlags(com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG) class StickyModifierStateListenerTest { @get:Rule |