Merge "[1/n] Compat UI status flag creation" into main
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 0321e1df..97f6899 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -1669,6 +1669,46 @@
}
/**
+ * Gets the mapping between the doze brightness sensor values and brightness values. The doze
+ * brightness sensor is a light sensor used to determine the brightness while the device is
+ * dozing. Light sensor values are typically integers in the rage of 0-4. The returned values
+ * are between {@link PowerManager#BRIGHTNESS_MIN} and {@link PowerManager#BRIGHTNESS_MAX}, or
+ * -1 meaning that the current brightness should be kept.
+ * <p>
+ * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS}
+ * permission.
+ * </p>
+ *
+ * @param displayId The ID of the display
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+ @Nullable
+ public float[] getDozeBrightnessSensorValueToBrightness(int displayId) {
+ return mGlobal.getDozeBrightnessSensorValueToBrightness(displayId);
+ }
+
+ /**
+ * Gets the default doze brightness.
+ * The returned values are between {@link PowerManager#BRIGHTNESS_MIN} and
+ * {@link PowerManager#BRIGHTNESS_MAX}.
+ * <p>
+ * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS}
+ * permission.
+ * </p>
+ *
+ * @param displayId The ID of the display
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+ @FloatRange(from = 0f, to = 1f)
+ public float getDefaultDozeBrightness(int displayId) {
+ return mGlobal.getDefaultDozeBrightness(displayId);
+ }
+
+ /**
* Listens for changes in available display devices.
*/
public interface DisplayListener {
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index e9cd37a..cae33d0 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -21,6 +21,7 @@
import static android.view.Display.HdrCapabilities.HdrType;
import android.Manifest;
+import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1226,6 +1227,32 @@
}
}
+ /**
+ * @see DisplayManager#getDozeBrightnessSensorValueToBrightness
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+ @Nullable
+ public float[] getDozeBrightnessSensorValueToBrightness(int displayId) {
+ try {
+ return mDm.getDozeBrightnessSensorValueToBrightness(displayId);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @see DisplayManager#getDefaultDozeBrightness
+ */
+ @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+ @FloatRange(from = 0f, to = 1f)
+ public float getDefaultDozeBrightness(int displayId) {
+ try {
+ return mDm.getDefaultDozeBrightness(displayId);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub {
@Override
public void onDisplayEvent(int displayId, @DisplayEvent int event) {
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 77277ee..f3c21e9f 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -246,4 +246,12 @@
// Restricts display modes to specified modeIds.
@EnforcePermission("RESTRICT_DISPLAY_MODES")
void requestDisplayModes(in IBinder token, int displayId, in @nullable int[] modeIds);
+
+ // Get the mapping between the doze brightness sensor values and brightness values
+ @EnforcePermission("CONTROL_DISPLAY_BRIGHTNESS")
+ float[] getDozeBrightnessSensorValueToBrightness(int displayId);
+
+ // Get the default doze brightness
+ @EnforcePermission("CONTROL_DISPLAY_BRIGHTNESS")
+ float getDefaultDozeBrightness(int displayId);
}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 406a1a6..026013c 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -564,8 +564,7 @@
BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM,
BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM,
BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT,
- BRIGHTNESS_CONSTRAINT_TYPE_DIM,
- BRIGHTNESS_CONSTRAINT_TYPE_DOZE
+ BRIGHTNESS_CONSTRAINT_TYPE_DIM
})
@Retention(RetentionPolicy.SOURCE)
public @interface BrightnessConstraint{}
@@ -594,12 +593,6 @@
public static final int BRIGHTNESS_CONSTRAINT_TYPE_DIM = 3;
/**
- * Brightness constraint type: minimum allowed value.
- * @hide
- */
- public static final int BRIGHTNESS_CONSTRAINT_TYPE_DOZE = 4;
-
- /**
* @hide
*/
@IntDef(prefix = { "WAKE_REASON_" }, value = {
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index c73a422..ad2f59d 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -46,6 +46,7 @@
flag {
namespace: "haptics"
name: "vibration_xml_apis"
+ is_exported: true
description: "Enabled System APIs for vibration effect XML parser and serializer"
bug: "347273158"
metadata {
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 0714285..d8a88b8 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -138,12 +138,6 @@
"settings_show_stylus_preferences";
/**
- * Flag to enable/disable biometrics enrollment v2
- * @hide
- */
- public static final String SETTINGS_BIOMETRICS2_ENROLLMENT = "settings_biometrics2_enrollment";
-
- /**
* Flag to enable/disable FingerprintSettings v2
* @hide
*/
@@ -223,7 +217,6 @@
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA_METRICS, "true");
DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "true");
- DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
DEFAULT_FLAGS.put(SETTINGS_PREFER_ACCESSIBILITY_MENU_IN_SYSTEM, "false");
DEFAULT_FLAGS.put(SETTINGS_AUDIO_ROUTING, "false");
DEFAULT_FLAGS.put(SETTINGS_FLASH_NOTIFICATIONS, "true");
diff --git a/core/java/android/window/ActivityWindowInfo.java b/core/java/android/window/ActivityWindowInfo.java
index 946bb82..71c500c 100644
--- a/core/java/android/window/ActivityWindowInfo.java
+++ b/core/java/android/window/ActivityWindowInfo.java
@@ -18,6 +18,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.ActivityThread;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
@@ -144,4 +146,15 @@
+ ", taskFragmentBounds=" + mTaskFragmentBounds
+ "}";
}
+
+ /** Gets the {@link ActivityWindowInfo} of the given activity. */
+ @Nullable
+ public static ActivityWindowInfo getActivityWindowInfo(@NonNull Activity activity) {
+ if (activity.isFinishing()) {
+ return null;
+ }
+ final ActivityThread.ActivityClientRecord record = ActivityThread.currentActivityThread()
+ .getActivityClient(activity.getActivityToken());
+ return record != null ? record.getActivityWindowInfo() : null;
+ }
}
diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl
index 2c64b8e..ac57c00 100644
--- a/core/java/android/window/ITaskFragmentOrganizerController.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl
@@ -39,12 +39,6 @@
void unregisterOrganizer(in ITaskFragmentOrganizer organizer);
/**
- * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and
- * only occupies a portion of Task bounds.
- */
- boolean isActivityEmbedded(in IBinder activityToken);
-
- /**
* Notifies the server that the organizer has finished handling the given transaction. The
* server should apply the given {@link WindowContainerTransaction} for the necessary changes.
*/
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index 15f1258..8e429cb 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -21,6 +21,7 @@
import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.window.ActivityWindowInfo.getActivityWindowInfo;
import android.annotation.CallSuper;
import android.annotation.FlaggedApi;
@@ -29,6 +30,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.TestApi;
+import android.app.Activity;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
@@ -38,6 +40,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
import java.util.concurrent.Executor;
/**
@@ -324,16 +327,15 @@
}
/**
- * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and
+ * Checks if an activity is organized by a {@link android.window.TaskFragmentOrganizer} and
* only occupies a portion of Task bounds.
+ *
+ * @see ActivityWindowInfo for additional window info.
* @hide
*/
- // TODO(b/287582673): cleanup
- public boolean isActivityEmbedded(@NonNull IBinder activityToken) {
- try {
- return getController().isActivityEmbedded(activityToken);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ public static boolean isActivityEmbedded(@NonNull Activity activity) {
+ Objects.requireNonNull(activity);
+ final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity);
+ return activityWindowInfo != null && activityWindowInfo.isEmbedded();
}
}
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index cc880e1..48fb2b3 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -91,6 +91,13 @@
}
flag {
+ name: "camera_compat_fullscreen_pick_same_task_activity"
+ namespace: "large_screen_experiences_app_compat"
+ description: "Limit undo of camera compat treatment to the same task that started the treatment."
+ bug: "350495350"
+}
+
+flag {
name: "app_compat_refactoring"
namespace: "large_screen_experiences_app_compat"
description: "Whether the changes about app compat refactoring are enabled./n"
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index c07fd38..7c62615 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -27,6 +27,7 @@
#include <android_media_audiopolicy.h>
#include <android_os_Parcel.h>
#include <audiomanager/AudioManager.h>
+#include <android-base/properties.h>
#include <binder/IBinder.h>
#include <jni.h>
#include <media/AidlConversion.h>
@@ -41,8 +42,10 @@
#include <system/audio_policy.h>
#include <utils/Log.h>
+#include <thread>
#include <optional>
#include <sstream>
+#include <memory>
#include <vector>
#include "android_media_AudioAttributes.h"
@@ -261,6 +264,13 @@
jfieldID mMixerBehavior;
} gAudioMixerAttributesField;
+static struct {
+ jclass clazz;
+ jmethodID run;
+} gRunnableClassInfo;
+
+static JavaVM* gVm;
+
static Mutex gLock;
enum AudioError {
@@ -3362,6 +3372,55 @@
return enabled;
}
+class JavaSystemPropertyListener {
+ public:
+ JavaSystemPropertyListener(JNIEnv* env, jobject javaCallback, std::string sysPropName) :
+ mCallback(env->NewGlobalRef(javaCallback)),
+ mCachedProperty(android::base::CachedProperty{std::move(sysPropName)}) {
+ mListenerThread = std::thread([this]() mutable {
+ JNIEnv* threadEnv = GetOrAttachJNIEnvironment(gVm);
+ while (!mCleanupSignal.load()) {
+ using namespace std::chrono_literals;
+ // 1s timeout so this thread can read the cleanup signal to (slowly) be able to
+ // be destroyed.
+ std::string newVal = mCachedProperty.WaitForChange(1000ms) ?: "";
+ if (newVal != "" && mLastVal != newVal) {
+ threadEnv->CallVoidMethod(mCallback, gRunnableClassInfo.run);
+ mLastVal = std::move(newVal);
+ }
+ }
+ });
+ }
+
+ ~JavaSystemPropertyListener() {
+ mCleanupSignal.store(true);
+ mListenerThread.join();
+ JNIEnv* env = GetOrAttachJNIEnvironment(gVm);
+ env->DeleteGlobalRef(mCallback);
+ }
+
+ private:
+ jobject mCallback;
+ android::base::CachedProperty mCachedProperty;
+ std::thread mListenerThread;
+ std::atomic<bool> mCleanupSignal{false};
+ std::string mLastVal = "";
+};
+
+std::vector<std::unique_ptr<JavaSystemPropertyListener>> gSystemPropertyListeners;
+std::mutex gSysPropLock{};
+
+static void android_media_AudioSystem_listenForSystemPropertyChange(JNIEnv *env, jobject thiz,
+ jstring sysProp,
+ jobject javaCallback) {
+ ScopedUtfChars sysPropChars{env, sysProp};
+ auto listener = std::make_unique<JavaSystemPropertyListener>(env, javaCallback,
+ std::string{sysPropChars.c_str()});
+ std::unique_lock _l{gSysPropLock};
+ gSystemPropertyListeners.push_back(std::move(listener));
+}
+
+
// ----------------------------------------------------------------------------
#define MAKE_AUDIO_SYSTEM_METHOD(x) \
@@ -3534,7 +3593,12 @@
android_media_AudioSystem_clearPreferredMixerAttributes),
MAKE_AUDIO_SYSTEM_METHOD(supportsBluetoothVariableLatency),
MAKE_AUDIO_SYSTEM_METHOD(setBluetoothVariableLatencyEnabled),
- MAKE_AUDIO_SYSTEM_METHOD(isBluetoothVariableLatencyEnabled)};
+ MAKE_AUDIO_SYSTEM_METHOD(isBluetoothVariableLatencyEnabled),
+ MAKE_JNI_NATIVE_METHOD("listenForSystemPropertyChange",
+ "(Ljava/lang/String;Ljava/lang/Runnable;)V",
+ android_media_AudioSystem_listenForSystemPropertyChange),
+
+ };
static const JNINativeMethod gEventHandlerMethods[] =
{MAKE_JNI_NATIVE_METHOD("native_setup", "(Ljava/lang/Object;)V",
@@ -3816,6 +3880,12 @@
gAudioMixerAttributesField.mMixerBehavior =
GetFieldIDOrDie(env, audioMixerAttributesClass, "mMixerBehavior", "I");
+ jclass runnableClazz = FindClassOrDie(env, "java/lang/Runnable");
+ gRunnableClassInfo.clazz = MakeGlobalRefOrDie(env, runnableClazz);
+ gRunnableClassInfo.run = GetMethodIDOrDie(env, runnableClazz, "run", "()V");
+
+ LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&gVm) != 0);
+
AudioSystem::addErrorCallback(android_media_AudioSystem_error_callback);
RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 982189e..1a1d83c 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -1063,8 +1063,8 @@
}
env->ReleaseStringUTFChars(file, file8);
- // Most proc files we read are small, so we only go through the
- // loop once and use the stack buffer. We allocate a buffer big
+ // Most proc files we read are small, so we go through the loop
+ // with the stack buffer firstly. We allocate a buffer big
// enough for the whole file.
char readBufferStack[kProcReadStackBufferSize];
@@ -1072,37 +1072,47 @@
char* readBuffer = &readBufferStack[0];
ssize_t readBufferSize = kProcReadStackBufferSize;
ssize_t numberBytesRead;
+ off_t offset = 0;
for (;;) {
+ ssize_t requestedBufferSize = readBufferSize - offset;
// By using pread, we can avoid an lseek to rewind the FD
// before retry, saving a system call.
- numberBytesRead = pread(fd, readBuffer, readBufferSize, 0);
- if (numberBytesRead < 0 && errno == EINTR) {
- continue;
- }
+ numberBytesRead =
+ TEMP_FAILURE_RETRY(pread(fd, readBuffer + offset, requestedBufferSize, offset));
if (numberBytesRead < 0) {
if (kDebugProc) {
- ALOGW("Unable to open process file: %s fd=%d\n", file8, fd.get());
+ ALOGW("Unable to read process file err: %s file: %s fd=%d\n",
+ strerror_r(errno, &readBufferStack[0], sizeof(readBufferStack)), file8,
+ fd.get());
}
return JNI_FALSE;
}
- if (numberBytesRead < readBufferSize) {
+ if (numberBytesRead == 0) {
+ // End of file.
+ numberBytesRead = offset;
break;
}
- if (readBufferSize > std::numeric_limits<ssize_t>::max() / 2) {
- if (kDebugProc) {
- ALOGW("Proc file too big: %s fd=%d\n", file8, fd.get());
+ if (numberBytesRead < requestedBufferSize) {
+ // Read less bytes than requested, it's not an error per pread(2).
+ offset += numberBytesRead;
+ } else {
+ // Buffer is fully used, try to grow it.
+ if (readBufferSize > std::numeric_limits<ssize_t>::max() / 2) {
+ if (kDebugProc) {
+ ALOGW("Proc file too big: %s fd=%d\n", file8, fd.get());
+ }
+ return JNI_FALSE;
}
- return JNI_FALSE;
+ readBufferSize = std::max(readBufferSize * 2, kProcReadMinHeapBufferSize);
+ readBufferHeap.reset(); // Free address space before getting more.
+ readBufferHeap = std::make_unique<char[]>(readBufferSize);
+ if (!readBufferHeap) {
+ jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+ return JNI_FALSE;
+ }
+ readBuffer = readBufferHeap.get();
+ offset = 0;
}
- readBufferSize = std::max(readBufferSize * 2,
- kProcReadMinHeapBufferSize);
- readBufferHeap.reset(); // Free address space before getting more.
- readBufferHeap = std::make_unique<char[]>(readBufferSize);
- if (!readBufferHeap) {
- jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
- return JNI_FALSE;
- }
- readBuffer = readBufferHeap.get();
}
// parseProcLineArray below modifies the buffer while parsing!
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index 59d18b8..30c926c 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -104,6 +104,7 @@
extern int register_android_view_KeyEvent(JNIEnv* env);
extern int register_android_view_InputDevice(JNIEnv* env);
extern int register_android_view_MotionEvent(JNIEnv* env);
+extern int register_android_view_Surface(JNIEnv* env);
extern int register_android_view_ThreadedRenderer(JNIEnv* env);
extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env);
extern int register_android_view_VelocityTracker(JNIEnv* env);
@@ -151,6 +152,7 @@
{"android.view.KeyEvent", REG_JNI(register_android_view_KeyEvent)},
{"android.view.InputDevice", REG_JNI(register_android_view_InputDevice)},
{"android.view.MotionEvent", REG_JNI(register_android_view_MotionEvent)},
+ {"android.view.Surface", REG_JNI(register_android_view_Surface)},
{"android.view.VelocityTracker", REG_JNI(register_android_view_VelocityTracker)},
{"com.android.internal.util.VirtualRefBasePtr",
REG_JNI(register_com_android_internal_util_VirtualRefBasePtr)},
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index b120723..8e1fde0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -22,6 +22,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.window.ActivityWindowInfo.getActivityWindowInfo;
import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
@@ -80,6 +81,7 @@
import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOperation;
+import android.window.TaskFragmentOrganizer;
import android.window.TaskFragmentParentInfo;
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerTransaction;
@@ -2553,9 +2555,9 @@
return ActivityThread.currentActivityThread().getActivity(activityToken);
}
- @VisibleForTesting
@Nullable
- ActivityThread.ActivityClientRecord getActivityClientRecord(@NonNull Activity activity) {
+ private ActivityThread.ActivityClientRecord getActivityClientRecord(
+ @NonNull Activity activity) {
return ActivityThread.currentActivityThread()
.getActivityClient(activity.getActivityToken());
}
@@ -3092,10 +3094,8 @@
*/
@Override
public boolean isActivityEmbedded(@NonNull Activity activity) {
- Objects.requireNonNull(activity);
synchronized (mLock) {
- final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity);
- return activityWindowInfo != null && activityWindowInfo.isEmbedded();
+ return TaskFragmentOrganizer.isActivityEmbedded(activity);
}
}
@@ -3165,15 +3165,6 @@
}
}
- @Nullable
- private ActivityWindowInfo getActivityWindowInfo(@NonNull Activity activity) {
- if (activity.isFinishing()) {
- return null;
- }
- final ActivityThread.ActivityClientRecord record = getActivityClientRecord(activity);
- return record != null ? record.getActivityWindowInfo() : null;
- }
-
@NonNull
private static EmbeddedActivityWindowInfo translateActivityWindowInfo(
@NonNull Activity activity, @NonNull ActivityWindowInfo activityWindowInfo) {
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 99c0ee2..d852204 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -165,6 +165,7 @@
private Consumer<List<SplitInfo>> mEmbeddingCallback;
private List<SplitInfo> mSplitInfos;
private TransactionManager mTransactionManager;
+ private ActivityThread mCurrentActivityThread;
@Before
public void setUp() {
@@ -181,10 +182,12 @@
};
mSplitController.setSplitInfoCallback(mEmbeddingCallback);
mTransactionManager = mSplitController.mTransactionManager;
+ mCurrentActivityThread = ActivityThread.currentActivityThread();
spyOn(mSplitController);
spyOn(mSplitPresenter);
spyOn(mEmbeddingCallback);
spyOn(mTransactionManager);
+ spyOn(mCurrentActivityThread);
doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean());
final Configuration activityConfig = new Configuration();
activityConfig.windowConfiguration.setBounds(TASK_BOUNDS);
@@ -1668,7 +1671,8 @@
final IBinder activityToken = new Binder();
doReturn(activityToken).when(activity).getActivityToken();
doReturn(activity).when(mSplitController).getActivity(activityToken);
- doReturn(activityClientRecord).when(mSplitController).getActivityClientRecord(activity);
+ doReturn(activityClientRecord).when(mCurrentActivityThread).getActivityClient(
+ activityToken);
doReturn(taskId).when(activity).getTaskId();
doReturn(new ActivityInfo()).when(activity).getActivityInfo();
doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIRepository.kt
new file mode 100644
index 0000000..cb54d89
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIRepository.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.api
+
+/**
+ * Abstraction for the repository of all the available CompatUISpec
+ */
+interface CompatUIRepository {
+ /**
+ * Adds a {@link CompatUISpec} to the repository
+ * @throws IllegalStateException in case of illegal spec
+ */
+ fun addSpec(spec: CompatUISpec)
+
+ /**
+ * Iterates on the list of available {@link CompatUISpec} invoking
+ * fn for each of them.
+ */
+ fun iterateOn(fn: (CompatUISpec) -> Unit)
+
+ /**
+ * Returns the {@link CompatUISpec} for a given key
+ */
+ fun findSpec(name: String): CompatUISpec?
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISpec.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISpec.kt
new file mode 100644
index 0000000..24c2c8c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISpec.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.api
+
+/**
+ * Describes each compat ui component to the framework.
+ */
+data class CompatUISpec(
+ // Unique name for the component. It's used for debug and for generating the
+ // unique component identifier in the system.
+ val name: String
+)
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
index a181eaf..8408ea6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
@@ -19,12 +19,15 @@
import com.android.wm.shell.compatui.api.CompatUIEvent
import com.android.wm.shell.compatui.api.CompatUIHandler
import com.android.wm.shell.compatui.api.CompatUIInfo
+import com.android.wm.shell.compatui.api.CompatUIRepository
import java.util.function.Consumer
/**
* Default implementation of {@link CompatUIHandler} to handle CompatUI components
*/
-class DefaultCompatUIHandler : CompatUIHandler {
+class DefaultCompatUIHandler(
+ private val compatUIRepository: CompatUIRepository
+) : CompatUIHandler {
private var compatUIEventSender: Consumer<CompatUIEvent>? = null
override fun onCompatInfoChanged(compatUIInfo: CompatUIInfo) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepository.kt
new file mode 100644
index 0000000..10d9425
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepository.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.impl
+
+import com.android.wm.shell.compatui.api.CompatUIRepository
+import com.android.wm.shell.compatui.api.CompatUISpec
+
+/**
+ * Default {@link CompatUIRepository} implementation
+ */
+class DefaultCompatUIRepository : CompatUIRepository {
+
+ private val allSpecs = mutableMapOf<String, CompatUISpec>()
+
+ override fun addSpec(spec: CompatUISpec) {
+ if (allSpecs[spec.name] != null) {
+ throw IllegalStateException("Spec with id:${spec.name} already present")
+ }
+ allSpecs[spec.name] = spec
+ }
+
+ override fun iterateOn(fn: (CompatUISpec) -> Unit) =
+ allSpecs.values.forEach(fn)
+
+ override fun findSpec(name: String): CompatUISpec? =
+ allSpecs[name]
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 9bdc0b2..4b548cb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -72,7 +72,9 @@
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.compatui.CompatUIShellCommandHandler;
import com.android.wm.shell.compatui.api.CompatUIHandler;
+import com.android.wm.shell.compatui.api.CompatUIRepository;
import com.android.wm.shell.compatui.impl.DefaultCompatUIHandler;
+import com.android.wm.shell.compatui.impl.DefaultCompatUIRepository;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -245,12 +247,13 @@
Lazy<DockStateReader> dockStateReader,
Lazy<CompatUIConfiguration> compatUIConfiguration,
Lazy<CompatUIShellCommandHandler> compatUIShellCommandHandler,
- Lazy<AccessibilityManager> accessibilityManager) {
+ Lazy<AccessibilityManager> accessibilityManager,
+ CompatUIRepository compatUIRepository) {
if (!context.getResources().getBoolean(R.bool.config_enableCompatUIController)) {
return Optional.empty();
}
if (Flags.appCompatUiFramework()) {
- return Optional.of(new DefaultCompatUIHandler());
+ return Optional.of(new DefaultCompatUIHandler(compatUIRepository));
}
return Optional.of(
new CompatUIController(
@@ -271,6 +274,12 @@
@WMSingleton
@Provides
+ static CompatUIRepository provideCompatUIRepository() {
+ return new DefaultCompatUIRepository();
+ }
+
+ @WMSingleton
+ @Provides
static SyncTransactionQueue provideSyncTransactionQueue(TransactionPool pool,
@ShellMainThread ShellExecutor mainExecutor) {
return new SyncTransactionQueue(pool, mainExecutor);
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 5807246..d46b2d6 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
@@ -1045,14 +1045,12 @@
/** Handle task closing by removing wallpaper activity if it's the last active task */
private fun handleTaskClosing(task: RunningTaskInfo): WindowContainerTransaction? {
- val wct = if (
- desktopModeTaskRepository.isOnlyVisibleNonClosingTask(task.taskId) &&
- desktopModeTaskRepository.wallpaperActivityToken != null
- ) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleTaskClosing")
+ val wct = WindowContainerTransaction()
+ if (desktopModeTaskRepository.isOnlyVisibleNonClosingTask(task.taskId)
+ && desktopModeTaskRepository.wallpaperActivityToken != null) {
// Remove wallpaper activity when the last active task is removed
- WindowContainerTransaction().also { wct -> removeWallpaperActivity(wct) }
- } else {
- null
+ removeWallpaperActivity(wct)
}
if (!desktopModeTaskRepository.addClosingTask(task.displayId, task.taskId)) {
// Could happen if the task hasn't been removed from closing list after it disappeared
@@ -1062,7 +1060,12 @@
task.taskId
)
}
- return wct
+ // If a CLOSE or TO_BACK is triggered on a desktop task, remove the task.
+ if (Flags.enableDesktopWindowingBackNavigation() &&
+ desktopModeTaskRepository.isVisibleTask(task.taskId)) {
+ wct.removeTask(task.token)
+ }
+ return if (wct.isEmpty) null else wct
}
private fun addMoveToDesktopChanges(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepositoryTest.kt
new file mode 100644
index 0000000..1a86cfd
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepositoryTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.impl
+
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.compatui.api.CompatUIRepository
+import com.android.wm.shell.compatui.api.CompatUISpec
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for {@link DefaultCompatUIRepository}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:DefaultCompatUIRepositoryTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class DefaultCompatUIRepositoryTest {
+
+ lateinit var repository: CompatUIRepository
+
+ @get:Rule
+ val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ @Before
+ fun setUp() {
+ repository = DefaultCompatUIRepository()
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun `addSpec throws exception with specs with duplicate id`() {
+ repository.addSpec(CompatUISpec("one"))
+ repository.addSpec(CompatUISpec("one"))
+ }
+
+ @Test
+ fun `iterateOn invokes the consumer`() {
+ with(repository) {
+ addSpec(CompatUISpec("one"))
+ addSpec(CompatUISpec("two"))
+ addSpec(CompatUISpec("three"))
+ val consumer = object : (CompatUISpec) -> Unit {
+ var acc = ""
+ override fun invoke(spec: CompatUISpec) {
+ acc += spec.name
+ }
+ }
+ iterateOn(consumer)
+ assertEquals("onetwothree", consumer.acc)
+ }
+ }
+
+ @Test
+ fun `findSpec returns existing specs`() {
+ with(repository) {
+ val one = CompatUISpec("one")
+ val two = CompatUISpec("two")
+ val three = CompatUISpec("three")
+ addSpec(one)
+ addSpec(two)
+ addSpec(three)
+ assertEquals(findSpec("one"), one)
+ assertEquals(findSpec("two"), two)
+ assertEquals(findSpec("three"), three)
+ assertNull(findSpec("abc"))
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUIRepository.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUIRepository.kt
new file mode 100644
index 0000000..cdc524a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUIRepository.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.impl
+
+import com.android.wm.shell.compatui.api.CompatUIRepository
+import com.android.wm.shell.compatui.api.CompatUISpec
+
+/**
+ * Fake implementation for {@link CompatUIRepository}
+ */
+class FakeCompatUIRepository : CompatUIRepository {
+ val allSpecs = mutableMapOf<String, CompatUISpec>()
+ override fun addSpec(spec: CompatUISpec) {
+ if (findSpec(spec.name) != null) {
+ throw IllegalStateException("Spec with name:${spec.name} already present")
+ }
+ allSpecs[spec.name] = spec
+ }
+
+ override fun iterateOn(fn: (CompatUISpec) -> Unit) =
+ allSpecs.values.forEach(fn)
+
+ override fun findSpec(name: String): CompatUISpec? =
+ allSpecs[name]
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 8558a77..c56671a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -20,7 +20,6 @@
import android.app.ActivityManager.RunningTaskInfo
import android.app.KeyguardManager
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
-import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
@@ -68,6 +67,7 @@
import com.android.dx.mockito.inline.extended.ExtendedMockito.never
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.window.flags.Flags
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
import com.android.wm.shell.MockToken
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
@@ -125,11 +125,11 @@
import org.mockito.Mockito.mock
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.capture
import org.mockito.quality.Strictness
+import org.mockito.Mockito.`when` as whenever
/**
* Test class for {@link DesktopTasksController}
@@ -1451,7 +1451,7 @@
.setActivityType(ACTIVITY_TYPE_STANDARD)
.setWindowingMode(WINDOWING_MODE_FULLSCREEN)
.build()
- val transition = createTransition(task = task, type = WindowManager.TRANSIT_CLOSE)
+ val transition = createTransition(task = task, type = TRANSIT_CLOSE)
val result = controller.handleRequest(Binder(), transition)
assertThat(result).isNull()
}
@@ -1545,8 +1545,11 @@
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_singleActiveTaskNoTokenFlagDisabled_doesNotHandle() {
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
+ )
+ fun handleRequest_backTransition_singleActiveTaskNoToken_wallpaperDisabled_backNavDisabled_doesNotHandle() {
val task = setUpFreeformTask()
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
@@ -1555,8 +1558,22 @@
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_singleActiveTaskNoToken_wallpaperEnabled_backNavEnabled_removesTask() {
+ val task = setUpFreeformTask()
+
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+ assertNotNull(result, "Should handle request").assertRemoveAt(0, task.token)
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_singleActiveTaskNoTokenFlagEnabled_doesNotHandle() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_backTransition_singleActiveTaskNoToken_backNavigationDisabled_doesNotHandle() {
val task = setUpFreeformTask()
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
@@ -1565,8 +1582,11 @@
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_singleActiveTaskWithTokenFlagDisabled_doesNotHandle() {
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_singleActiveTaskWithToken_wallpaperDisabled_backNavDisabled_doesNotHandle() {
val task = setUpFreeformTask()
desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
@@ -1576,22 +1596,42 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_singleActiveTaskWithTokenFlagEnabled_handlesRequest() {
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_singleActiveTaskWithToken_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() {
val task = setUpFreeformTask()
val wallpaperToken = MockToken().token()
desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
- assertNotNull(result, "Should handle request")
- // Should create remove wallpaper transaction
- .assertRemoveAt(index = 0, wallpaperToken)
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ result.assertRemoveAt(index = 1, task.token)
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_multipleActiveTasksFlagDisabled_doesNotHandle() {
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_backTransition_singleActiveTaskWithToken_backNavigationDisabled_removesWallpaper() {
+ val task = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_multipleTasks_wallpaperDisabled_backNavDisabled_doesNotHandle() {
val task1 = setUpFreeformTask()
setUpFreeformTask()
@@ -1602,8 +1642,24 @@
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_multipleTasks_wallpaperEnabled_backNavEnabled_removesTask() {
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+
+ desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
+
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, task1.token)
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_multipleActiveTasksFlagEnabled_doesNotHandle() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_backTransition_multipleTasks_backNavigationDisabled_doesNotHandle() {
val task1 = setUpFreeformTask()
setUpFreeformTask()
@@ -1614,8 +1670,11 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_multipleActiveTasksSingleNonClosing_handlesRequest() {
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_multipleTasksSingleNonClosing_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val wallpaperToken = MockToken().token()
@@ -1624,14 +1683,33 @@
desktopModeTaskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
- assertNotNull(result, "Should handle request")
- // Should create remove wallpaper transaction
- .assertRemoveAt(index = 0, wallpaperToken)
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ result.assertRemoveAt(index = 1, task1.token)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_multipleActiveTasksSingleNonMinimized_handlesRequest() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_backTransition_multipleTasksSingleNonClosing_backNavigationDisabled_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_multipleTasksSingleNonMinimized_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val wallpaperToken = MockToken().token()
@@ -1640,24 +1718,53 @@
desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
- assertNotNull(result, "Should handle request")
- // Should create remove wallpaper transaction
- .assertRemoveAt(index = 0, wallpaperToken)
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_singleActiveTaskNoTokenFlagDisabled_doesNotHandle() {
- val task = setUpFreeformTask()
-
- val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
-
- assertNull(result, "Should not handle request")
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ result.assertRemoveAt(index = 1, task1.token)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_singleActiveTaskNoTokenFlagEnabled_doesNotHandle() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_backTransition_multipleTasksSingleNonMinimized_backNavigationDisabled_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_backTransition_nonMinimizadTask_wallpaperEnabled_backNavEnabled_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ // Task is being minimized so mark it as not visible.
+ desktopModeTaskRepository
+ .updateVisibleFreeformTasks(displayId = DEFAULT_DISPLAY, task2.taskId, false)
+ val result = controller.handleRequest(Binder(), createTransition(task2, type = TRANSIT_TO_BACK))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_singleActiveTaskNoToken_wallpaperDisabled_backNavDisabled_doesNotHandle() {
val task = setUpFreeformTask()
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
@@ -1666,8 +1773,35 @@
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_singleActiveTaskWithTokenFlagDisabled_doesNotHandle() {
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_singleActiveTaskNoToken_wallpaperEnabled_backNavEnabled_removesTask() {
+ val task = setUpFreeformTask()
+
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, task.token)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_closeTransition_singleActiveTaskNoToken_backNavigationDisabled_doesNotHandle() {
+ val task = setUpFreeformTask()
+
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_singleActiveTaskWithToken_wallpaperDisabled_backNavDisabled_doesNotHandle() {
val task = setUpFreeformTask()
desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
@@ -1677,22 +1811,71 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_singleActiveTaskWithTokenFlagEnabled_handlesRequest() {
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_singleActiveTaskWithToken_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() {
val task = setUpFreeformTask()
val wallpaperToken = MockToken().token()
desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ result.assertRemoveAt(index = 1, task.token)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_closeTransition_singleActiveTaskWithToken_backNavigationDisabled_removesWallpaper() {
+ val task = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_multipleTasks_wallpaperDisabled_backNavDisabled_doesNotHandle() {
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+
+ desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_multipleTasks_wallpaperEnabled_backNavEnabled_removesTask() {
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+
+ desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
assertNotNull(result, "Should handle request")
- // Should create remove wallpaper transaction
- .assertRemoveAt(index = 0, wallpaperToken)
+ result.assertRemoveAt(index = 0, task1.token)
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_multipleActiveTasksFlagDisabled_doesNotHandle() {
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_closeTransition_multipleTasksFlagEnabled_backNavigationDisabled_doesNotHandle() {
val task1 = setUpFreeformTask()
setUpFreeformTask()
@@ -1703,20 +1886,11 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_multipleActiveTasksFlagEnabled_doesNotHandle() {
- val task1 = setUpFreeformTask()
- setUpFreeformTask()
-
- desktopModeTaskRepository.wallpaperActivityToken = MockToken().token()
- val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
-
- assertNull(result, "Should not handle request")
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_multipleActiveTasksSingleNonClosing_handlesRequest() {
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_multipleTasksSingleNonClosing_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val wallpaperToken = MockToken().token()
@@ -1725,14 +1899,33 @@
desktopModeTaskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
- assertNotNull(result, "Should handle request")
- // Should create remove wallpaper transaction
- .assertRemoveAt(index = 0, wallpaperToken)
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ result.assertRemoveAt(index = 1, task1.token)
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_multipleActiveTasksSingleNonMinimized_handlesRequest() {
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_closeTransition_multipleTasksSingleNonClosing_backNavigationDisabled_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_multipleTasksOneNonMinimized_wallpaperEnabled_backNavEnabled_removesWallpaperAndTask() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
val wallpaperToken = MockToken().token()
@@ -1741,9 +1934,45 @@
desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
- assertNotNull(result, "Should handle request")
- // Should create remove wallpaper transaction
- .assertRemoveAt(index = 0, wallpaperToken)
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ result.assertRemoveAt(index = 1, task1.token)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun handleRequest_closeTransition_multipleTasksSingleNonMinimized_backNavigationDisabled_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
+ )
+ fun handleRequest_closeTransition_minimizadTask_wallpaperEnabled_backNavEnabled_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ // Task is being minimized so mark it as not visible.
+ desktopModeTaskRepository
+ .updateVisibleFreeformTasks(displayId = DEFAULT_DISPLAY, task2.taskId, false)
+ val result = controller.handleRequest(Binder(), createTransition(task2, type = TRANSIT_TO_BACK))
+
+ assertNull(result, "Should not handle request")
}
@Test
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index d148afd..52b5ff7 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -2649,4 +2649,11 @@
* @hide
*/
public static native boolean isBluetoothVariableLatencyEnabled();
+
+ /**
+ * Register a native listener for system property sysprop
+ * @param callback the listener which fires when the property changes
+ * @hide
+ */
+ public static native void listenForSystemPropertyChange(String sysprop, Runnable callback);
}
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index 4059291..999f40e5 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -312,6 +312,10 @@
* <p>Once a MediaProjection has been stopped, it's up to the application to release any
* resources it may be holding (e.g. releasing the {@link VirtualDisplay} and
* {@link Surface}).
+ *
+ * <p>After this callback any call to
+ * {@link MediaProjection#createVirtualDisplay} will fail, even if no such
+ * {@link VirtualDisplay} was ever created for this MediaProjection session.
*/
public void onStop() { }
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 7ed67dc..4013d84 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -43,25 +43,31 @@
/**
* Manages the retrieval of certain types of {@link MediaProjection} tokens.
*
- * <p><ol>An example flow of starting a media projection will be:
- * <li>Declare a foreground service with the type {@code mediaProjection} in
- * the {@code AndroidManifest.xml}.
- * </li>
- * <li>Create an intent by calling {@link MediaProjectionManager#createScreenCaptureIntent()}
- * and pass this intent to {@link Activity#startActivityForResult(Intent, int)}.
- * </li>
- * <li>On getting {@link Activity#onActivityResult(int, int, Intent)},
- * start the foreground service with the type
- * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION}.
- * </li>
- * <li>Retrieve the media projection token by calling
- * {@link MediaProjectionManager#getMediaProjection(int, Intent)} with the result code and
- * intent from the {@link Activity#onActivityResult(int, int, Intent)} above.
- * </li>
- * <li>Start the screen capture session for media projection by calling
- * {@link MediaProjection#createVirtualDisplay(String, int, int, int, int, Surface,
- * android.hardware.display.VirtualDisplay.Callback, Handler)}.
- * </li>
+ * <p>
+ *
+ * <ol>
+ * An example flow of starting a media projection will be:
+ * <li>Declare a foreground service with the type {@code mediaProjection} in the {@code
+ * AndroidManifest.xml}.
+ * <li>Create an intent by calling {@link MediaProjectionManager#createScreenCaptureIntent()} and
+ * pass this intent to {@link Activity#startActivityForResult(Intent, int)}.
+ * <li>On getting {@link Activity#onActivityResult(int, int, Intent)}, start the foreground
+ * service with the type {@link
+ * android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION}.
+ * <li>Retrieve the media projection token by calling {@link
+ * MediaProjectionManager#getMediaProjection(int, Intent)} with the result code and intent
+ * from the {@link Activity#onActivityResult(int, int, Intent)} above.
+ * <li>Register a {@link MediaProjection.Callback} by calling {@link
+ * MediaProjection#registerCallback(MediaProjection.Callback, Handler)}. This is required to
+ * receive notifications about when the {@link MediaProjection} or captured content changes
+ * state. When receiving an `onStop()` callback, the client must clean up any resources it is
+ * holding, e.g. the {@link VirtualDisplay} and {@link Surface}. The MediaProjection may
+ * further no longer create any new {@link VirtualDisplay}s via {@link
+ * MediaProjection#createVirtualDisplay(String, int, int, int, int, Surface,
+ * VirtualDisplay.Callback, Handler)}.
+ * <li>Start the screen capture session for media projection by calling {@link
+ * MediaProjection#createVirtualDisplay(String, int, int, int, int, Surface,
+ * android.hardware.display.VirtualDisplay.Callback, Handler)}.
* </ol>
*/
@SystemService(Context.MEDIA_PROJECTION_SERVICE)
diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index df5644b..2645360 100644
--- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -46,20 +46,6 @@
</intent-filter>
</provider>
- <provider android:name="com.android.settingslib.spa.slice.SpaSliceProvider"
- android:authorities="com.android.spa.gallery.slice.provider"
- android:exported="true" >
- <intent-filter>
- <action android:name="android.intent.action.VIEW" />
- <category android:name="android.app.slice.category.SLICE" />
- </intent-filter>
- </provider>
-
- <receiver
- android:name="com.android.settingslib.spa.slice.SpaSliceBroadcastReceiver"
- android:exported="false">
- </receiver>
-
<activity
android:name="com.android.settingslib.spa.debug.BlankActivity"
android:exported="true">
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 91bd791..ffd2879 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -55,7 +55,6 @@
import com.android.settingslib.spa.gallery.ui.CopyablePageProvider
import com.android.settingslib.spa.gallery.scaffold.ScrollablePagerPageProvider
import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
-import com.android.settingslib.spa.slice.SpaSliceBroadcastReceiver
/**
* Enum to define all SPP name here.
@@ -120,9 +119,7 @@
override val logger = DebugLogger()
override val browseActivityClass = GalleryMainActivity::class.java
- override val sliceBroadcastReceiverClass = SpaSliceBroadcastReceiver::class.java
// For debugging
override val searchProviderAuthorities = "com.android.spa.gallery.search.provider"
- override val sliceProviderAuthorities = "com.android.spa.gallery.slice.provider"
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt
index 96de1a7..6d1d346 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt
@@ -27,7 +27,6 @@
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.common.EntrySearchData
-import com.android.settingslib.spa.framework.common.EntrySliceData
import com.android.settingslib.spa.framework.common.EntryStatusData
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
@@ -35,10 +34,8 @@
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.framework.util.createIntent
import com.android.settingslib.spa.gallery.R
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_SUMMARY
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_TITLE
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.AUTO_UPDATE_PREFERENCE_TITLE
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.DISABLE_PREFERENCE_SUMMARY
@@ -48,15 +45,10 @@
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_KEYWORDS
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_SUMMARY
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_TITLE
-import com.android.settingslib.spa.slice.createBrowsePendingIntent
-import com.android.settingslib.spa.slice.provider.createDemoActionSlice
-import com.android.settingslib.spa.slice.provider.createDemoBrowseSlice
-import com.android.settingslib.spa.slice.provider.createDemoSlice
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro
import com.android.settingslib.spa.widget.ui.SettingsIcon
-import kotlinx.coroutines.delay
private const val TAG = "PreferencePage"
@@ -139,26 +131,6 @@
override val enabled = { model.asyncEnable.value }
}
)
- }
- .setSliceDataFn { sliceUri, _ ->
- val createSliceImpl = { s: String ->
- createDemoBrowseSlice(
- sliceUri = sliceUri,
- title = ASYNC_PREFERENCE_TITLE,
- summary = s,
- )
- }
- return@setSliceDataFn object : EntrySliceData() {
- init {
- postValue(createSliceImpl("(loading)"))
- }
-
- override suspend fun asyncRunner() {
- spaLogger.message(TAG, "Async entry loading")
- delay(2000L)
- postValue(createSliceImpl(ASYNC_PREFERENCE_SUMMARY))
- }
- }
}.build()
)
entryList.add(
@@ -176,28 +148,6 @@
}
}
)
- }
- .setSliceDataFn { sliceUri, args ->
- val createSliceImpl = { v: Int ->
- createDemoActionSlice(
- sliceUri = sliceUri,
- title = MANUAL_UPDATE_PREFERENCE_TITLE,
- summary = "manual update value $v",
- )
- }
-
- return@setSliceDataFn object : EntrySliceData() {
- private var tick = args?.getString("init")?.toInt() ?: 0
-
- init {
- postValue(createSliceImpl(tick))
- }
-
- override suspend fun asyncAction() {
- tick++
- postValue(createSliceImpl(tick))
- }
- }
}.build()
)
entryList.add(
@@ -216,33 +166,6 @@
}
}
)
- }
- .setSliceDataFn { sliceUri, args ->
- val createSliceImpl = { v: Int ->
- createDemoBrowseSlice(
- sliceUri = sliceUri,
- title = AUTO_UPDATE_PREFERENCE_TITLE,
- summary = "auto update value $v",
- )
- }
-
- return@setSliceDataFn object : EntrySliceData() {
- private var tick = args?.getString("init")?.toInt() ?: 0
-
- init {
- postValue(createSliceImpl(tick))
- }
-
- override suspend fun asyncRunner() {
- spaLogger.message(TAG, "autoUpdater.active")
- while (true) {
- delay(1000L)
- tick++
- spaLogger.message(TAG, "autoUpdater.value $tick")
- postValue(createSliceImpl(tick))
- }
- }
- }
}.build()
)
@@ -272,22 +195,6 @@
clickRoute = SettingsPageProviderEnum.PREFERENCE.name
)
}
- .setSliceDataFn { sliceUri, _ ->
- val intent = owner.createIntent()?.createBrowsePendingIntent()
- ?: return@setSliceDataFn null
- return@setSliceDataFn object : EntrySliceData() {
- init {
- postValue(
- createDemoSlice(
- sliceUri = sliceUri,
- title = PAGE_TITLE,
- summary = "Injected Entry",
- intent = intent,
- )
- )
- }
- }
- }
}
override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
index 2d956d5..6e5132b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
@@ -17,12 +17,10 @@
package com.android.settingslib.spa.framework.common
import android.app.Activity
-import android.content.BroadcastReceiver
import android.content.Context
import android.util.Log
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
-import com.android.settingslib.spa.slice.SettingsSliceDataRepository
private const val TAG = "SpaEnvironment"
@@ -69,8 +67,6 @@
val entryRepository = lazy { SettingsEntryRepository(pageProviderRepository.value) }
- val sliceDataRepository = lazy { SettingsSliceDataRepository(entryRepository.value) }
-
// The application context. Use local context as fallback when applicationContext is not
// available (e.g. in Robolectric test).
val appContext: Context = context.applicationContext ?: context
@@ -81,11 +77,9 @@
// Specify class name of browse activity and slice broadcast receiver, which is used to
// generate the necessary intents.
open val browseActivityClass: Class<out Activity>? = null
- open val sliceBroadcastReceiverClass: Class<out BroadcastReceiver>? = null
// Specify provider authorities for debugging purpose.
open val searchProviderAuthorities: String? = null
- open val sliceProviderAuthorities: String? = null
// TODO: add other environment setup here.
companion object {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
deleted file mode 100644
index 7a4750d..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2022 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.settingslib.spa.slice
-
-import android.net.Uri
-import android.util.Log
-import com.android.settingslib.spa.framework.common.EntrySliceData
-import com.android.settingslib.spa.framework.common.SettingsEntryRepository
-import com.android.settingslib.spa.framework.util.getEntryId
-
-private const val TAG = "SliceDataRepository"
-
-class SettingsSliceDataRepository(private val entryRepository: SettingsEntryRepository) {
- // The map of slice uri to its EntrySliceData, a.k.a. LiveData<Slice?>
- private val sliceDataMap: MutableMap<String, EntrySliceData> = mutableMapOf()
-
- // Note: mark this function synchronized, so that we can get the same livedata during the
- // whole lifecycle of a Slice.
- @Synchronized
- fun getOrBuildSliceData(sliceUri: Uri): EntrySliceData? {
- val sliceString = sliceUri.getSliceId() ?: return null
- return sliceDataMap[sliceString] ?: buildLiveDataImpl(sliceUri)?.let {
- sliceDataMap[sliceString] = it
- it
- }
- }
-
- fun getActiveSliceData(sliceUri: Uri): EntrySliceData? {
- val sliceString = sliceUri.getSliceId() ?: return null
- val sliceData = sliceDataMap[sliceString] ?: return null
- return if (sliceData.isActive()) sliceData else null
- }
-
- private fun buildLiveDataImpl(sliceUri: Uri): EntrySliceData? {
- Log.d(TAG, "buildLiveData: $sliceUri")
-
- val entryId = sliceUri.getEntryId() ?: return null
- val entry = entryRepository.getEntry(entryId) ?: return null
- if (!entry.hasSliceSupport) return null
- val arguments = sliceUri.getRuntimeArguments()
- return entry.getSliceData(runtimeArguments = arguments, sliceUri = sliceUri)
- }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
index f362890..ec89c7c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
@@ -16,23 +16,10 @@
package com.android.settingslib.spa.slice
-import android.app.Activity
-import android.app.PendingIntent
-import android.content.BroadcastReceiver
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
import android.net.Uri
import android.os.Bundle
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.util.KEY_DESTINATION
import com.android.settingslib.spa.framework.util.KEY_HIGHLIGHT_ENTRY
-import com.android.settingslib.spa.framework.util.SESSION_SLICE
-import com.android.settingslib.spa.framework.util.SPA_INTENT_RESERVED_KEYS
-import com.android.settingslib.spa.framework.util.appendSpaParams
-import com.android.settingslib.spa.framework.util.getDestination
-import com.android.settingslib.spa.framework.util.getEntryId
// Defines SliceUri, which contains special query parameters:
// -- KEY_DESTINATION: The route that this slice is navigated to.
@@ -45,25 +32,6 @@
return getQueryParameter(KEY_HIGHLIGHT_ENTRY)
}
-fun SliceUri.getDestination(): String? {
- return getQueryParameter(KEY_DESTINATION)
-}
-
-fun SliceUri.getRuntimeArguments(): Bundle {
- val params = Bundle()
- for (queryName in queryParameterNames) {
- if (SPA_INTENT_RESERVED_KEYS.contains(queryName)) continue
- params.putString(queryName, getQueryParameter(queryName))
- }
- return params
-}
-
-fun SliceUri.getSliceId(): String? {
- val entryId = getEntryId() ?: return null
- val params = getRuntimeArguments()
- return "${entryId}_$params"
-}
-
fun Uri.Builder.appendSpaParams(
destination: String? = null,
entryId: String? = null,
@@ -79,72 +47,3 @@
return this
}
-fun Uri.Builder.fromEntry(
- entry: SettingsEntry,
- authority: String?,
- runtimeArguments: Bundle? = null
-): Uri.Builder {
- if (authority == null) return this
- val sp = entry.containerPage()
- return scheme("content").authority(authority).appendSpaParams(
- destination = sp.buildRoute(),
- entryId = entry.id,
- runtimeArguments = runtimeArguments
- )
-}
-
-fun SliceUri.createBroadcastPendingIntent(): PendingIntent? {
- val context = SpaEnvironmentFactory.instance.appContext
- val sliceBroadcastClass =
- SpaEnvironmentFactory.instance.sliceBroadcastReceiverClass ?: return null
- val entryId = getEntryId() ?: return null
- return createBroadcastPendingIntent(context, sliceBroadcastClass, entryId)
-}
-
-fun SliceUri.createBrowsePendingIntent(): PendingIntent? {
- val context = SpaEnvironmentFactory.instance.appContext
- val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
- val destination = getDestination() ?: return null
- val entryId = getEntryId()
- return createBrowsePendingIntent(context, browseActivityClass, destination, entryId)
-}
-
-fun Intent.createBrowsePendingIntent(): PendingIntent? {
- val context = SpaEnvironmentFactory.instance.appContext
- val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
- val destination = getDestination() ?: return null
- val entryId = getEntryId()
- return createBrowsePendingIntent(context, browseActivityClass, destination, entryId)
-}
-
-private fun createBrowsePendingIntent(
- context: Context,
- browseActivityClass: Class<out Activity>,
- destination: String,
- entryId: String?
-): PendingIntent {
- val intent = Intent().setComponent(ComponentName(context, browseActivityClass))
- .appendSpaParams(destination, entryId, SESSION_SLICE)
- .apply {
- // Set both extra and data (which is a Uri) in Slice Intent:
- // 1) extra is used in SPA navigation framework
- // 2) data is used in Slice framework
- data = Uri.Builder().appendSpaParams(destination, entryId).build()
- flags = Intent.FLAG_ACTIVITY_NEW_TASK
- }
-
- return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
-}
-
-private fun createBroadcastPendingIntent(
- context: Context,
- sliceBroadcastClass: Class<out BroadcastReceiver>,
- entryId: String
-): PendingIntent {
- val intent = Intent().setComponent(ComponentName(context, sliceBroadcastClass))
- .apply { data = Uri.Builder().appendSpaParams(entryId = entryId).build() }
- return PendingIntent.getBroadcast(
- context, 0 /* requestCode */, intent,
- PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE
- )
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceBroadcastReceiver.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceBroadcastReceiver.kt
deleted file mode 100644
index 39cb431..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceBroadcastReceiver.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2022 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.settingslib.spa.slice
-
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-
-class SpaSliceBroadcastReceiver : BroadcastReceiver() {
- override fun onReceive(context: Context?, intent: Intent?) {
- val sliceRepository by SpaEnvironmentFactory.instance.sliceDataRepository
- val sliceUri = intent?.data ?: return
- val sliceData = sliceRepository.getActiveSliceData(sliceUri) ?: return
- sliceData.doAction()
- }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceProvider.kt
deleted file mode 100644
index 3496f02..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SpaSliceProvider.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2022 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.settingslib.spa.slice
-
-import android.net.Uri
-import android.util.Log
-import androidx.lifecycle.Observer
-import androidx.slice.Slice
-import androidx.slice.SliceProvider
-import com.android.settingslib.spa.framework.common.EntrySliceData
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.withContext
-
-private const val TAG = "SpaSliceProvider"
-
-class SpaSliceProvider : SliceProvider(), Observer<Slice?> {
- private fun getOrPutSliceData(sliceUri: Uri): EntrySliceData? {
- if (!SpaEnvironmentFactory.isReady()) return null
- val sliceRepository by SpaEnvironmentFactory.instance.sliceDataRepository
- return sliceRepository.getOrBuildSliceData(sliceUri)
- }
-
- override fun onBindSlice(sliceUri: Uri): Slice? {
- if (context == null) return null
- Log.d(TAG, "onBindSlice: $sliceUri")
- return getOrPutSliceData(sliceUri)?.value
- }
-
- override fun onSlicePinned(sliceUri: Uri) {
- Log.d(TAG, "onSlicePinned: $sliceUri")
- super.onSlicePinned(sliceUri)
- val sliceLiveData = getOrPutSliceData(sliceUri) ?: return
- runBlocking {
- withContext(Dispatchers.Main) {
- sliceLiveData.observeForever(this@SpaSliceProvider)
- }
- }
- }
-
- override fun onSliceUnpinned(sliceUri: Uri) {
- Log.d(TAG, "onSliceUnpinned: $sliceUri")
- super.onSliceUnpinned(sliceUri)
- val sliceLiveData = getOrPutSliceData(sliceUri) ?: return
- runBlocking {
- withContext(Dispatchers.Main) {
- sliceLiveData.removeObserver(this@SpaSliceProvider)
- }
- }
- }
-
- override fun onChanged(value: Slice?) {
- val uri = value?.uri ?: return
- Log.d(TAG, "onChanged: $uri")
- context?.contentResolver?.notifyChange(uri, null)
- }
-
- override fun onCreateSliceProvider(): Boolean {
- Log.d(TAG, "onCreateSliceProvider")
- return true
- }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt
deleted file mode 100644
index 007f47b..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2022 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.settingslib.spa.slice.presenter
-
-import android.net.Uri
-import androidx.compose.material3.HorizontalDivider
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.viewinterop.AndroidView
-import androidx.lifecycle.compose.LocalLifecycleOwner
-import androidx.slice.widget.SliceLiveData
-import androidx.slice.widget.SliceView
-
-@Composable
-fun SliceDemo(sliceUri: Uri) {
- val context = LocalContext.current
- val lifecycleOwner = LocalLifecycleOwner.current
- val sliceData = remember {
- SliceLiveData.fromUri(context, sliceUri)
- }
-
- HorizontalDivider()
- AndroidView(
- factory = { localContext ->
- val view = SliceView(localContext)
- view.setShowTitleItems(true)
- view.isScrollable = false
- view
- },
- update = { view -> sliceData.observe(lifecycleOwner, view) }
- )
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt
deleted file mode 100644
index e4a7386..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2022 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.settingslib.spa.slice.provider
-
-import android.app.PendingIntent
-import android.content.Context
-import android.net.Uri
-import androidx.core.R
-import androidx.core.graphics.drawable.IconCompat
-import androidx.slice.Slice
-import androidx.slice.SliceManager
-import androidx.slice.builders.ListBuilder
-import androidx.slice.builders.SliceAction
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.slice.createBroadcastPendingIntent
-import com.android.settingslib.spa.slice.createBrowsePendingIntent
-
-fun createDemoBrowseSlice(sliceUri: Uri, title: String, summary: String): Slice? {
- val intent = sliceUri.createBrowsePendingIntent() ?: return null
- return createDemoSlice(sliceUri, title, summary, intent)
-}
-
-fun createDemoActionSlice(sliceUri: Uri, title: String, summary: String): Slice? {
- val intent = sliceUri.createBroadcastPendingIntent() ?: return null
- return createDemoSlice(sliceUri, title, summary, intent)
-}
-
-fun createDemoSlice(sliceUri: Uri, title: String, summary: String, intent: PendingIntent): Slice? {
- val context = SpaEnvironmentFactory.instance.appContext
- if (!SliceManager.getInstance(context).pinnedSlices.contains(sliceUri)) return null
- return ListBuilder(context, sliceUri, ListBuilder.INFINITY)
- .addRow(ListBuilder.RowBuilder().apply {
- setPrimaryAction(createSliceAction(context, intent))
- setTitle(title)
- setSubtitle(summary)
- }).build()
-}
-
-private fun createSliceAction(context: Context, intent: PendingIntent): SliceAction {
- return SliceAction.create(
- intent,
- IconCompat.createWithResource(context, R.drawable.notification_action_background),
- ListBuilder.ICON_IMAGE,
- "Enter app"
- )
-}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
deleted file mode 100644
index 341a4a5..0000000
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2022 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.settingslib.spa.slice
-
-import android.content.Context
-import android.net.Uri
-import androidx.arch.core.executor.testing.InstantTaskExecutorRule
-import androidx.lifecycle.Observer
-import androidx.slice.Slice
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.common.createSettingsPage
-import com.android.settingslib.spa.framework.util.genEntryId
-import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
-import com.android.settingslib.spa.tests.testutils.SppHome
-import com.android.settingslib.spa.tests.testutils.SppLayer2
-import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-class SettingsSliceDataRepositoryTest {
- @get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
-
- private val context: Context = ApplicationProvider.getApplicationContext()
- private val spaEnvironment =
- SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage()))
- private val sliceDataRepository by spaEnvironment.sliceDataRepository
-
- @Test
- fun getOrBuildSliceDataTest() {
- SpaEnvironmentFactory.reset(spaEnvironment)
-
- // Slice empty
- assertThat(sliceDataRepository.getOrBuildSliceData(Uri.EMPTY)).isNull()
-
- // Slice supported
- val page = SppLayer2.createSettingsPage()
- val entryId = genEntryId("Layer2Entry1", page)
- val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build()
- assertThat(sliceUri.getDestination()).isEqualTo("SppLayer2")
- assertThat(sliceUri.getSliceId()).isEqualTo("${entryId}_Bundle[{}]")
- val sliceData = sliceDataRepository.getOrBuildSliceData(sliceUri)
- assertThat(sliceData).isNotNull()
- assertThat(sliceDataRepository.getOrBuildSliceData(sliceUri)).isSameInstanceAs(sliceData)
-
- // Slice unsupported
- val entryId2 = genEntryId("Layer2Entry2", page)
- val sliceUri2 = Uri.Builder().appendSpaParams(page.buildRoute(), entryId2).build()
- assertThat(sliceUri2.getDestination()).isEqualTo("SppLayer2")
- assertThat(sliceUri2.getSliceId()).isEqualTo("${entryId2}_Bundle[{}]")
- assertThat(sliceDataRepository.getOrBuildSliceData(sliceUri2)).isNull()
- }
-
- @Test
- fun getActiveSliceDataTest() {
- SpaEnvironmentFactory.reset(spaEnvironment)
-
- val page = SppLayer2.createSettingsPage()
- val entryId = genEntryId("Layer2Entry1", page)
- val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build()
-
- // build slice data first
- val sliceData = sliceDataRepository.getOrBuildSliceData(sliceUri)
-
- // slice data is inactive
- assertThat(sliceData!!.isActive()).isFalse()
- assertThat(sliceDataRepository.getActiveSliceData(sliceUri)).isNull()
-
- // slice data is active
- val observer = Observer<Slice?> { }
- sliceData.observeForever(observer)
- assertThat(sliceData.isActive()).isTrue()
- assertThat(sliceDataRepository.getActiveSliceData(sliceUri)).isSameInstanceAs(sliceData)
-
- // slice data is inactive again
- sliceData.removeObserver(observer)
- assertThat(sliceData.isActive()).isFalse()
- assertThat(sliceDataRepository.getActiveSliceData(sliceUri)).isNull()
- }
-}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt
index d1c4e51..b489afd 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt
@@ -16,91 +16,27 @@
package com.android.settingslib.spa.slice
-import android.content.Context
-import android.content.Intent
import android.net.Uri
import androidx.core.os.bundleOf
-import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SliceUtilTest {
- private val context: Context = ApplicationProvider.getApplicationContext()
- private val spaEnvironment = SpaEnvironmentForTest(context)
-
@Test
fun sliceUriTest() {
assertThat(Uri.EMPTY.getEntryId()).isNull()
- assertThat(Uri.EMPTY.getDestination()).isNull()
- assertThat(Uri.EMPTY.getRuntimeArguments().size()).isEqualTo(0)
- assertThat(Uri.EMPTY.getSliceId()).isNull()
// valid slice uri
val dest = "myRoute"
val entryId = "myEntry"
val sliceUriWithoutParams = Uri.Builder().appendSpaParams(dest, entryId).build()
assertThat(sliceUriWithoutParams.getEntryId()).isEqualTo(entryId)
- assertThat(sliceUriWithoutParams.getDestination()).isEqualTo(dest)
- assertThat(sliceUriWithoutParams.getRuntimeArguments().size()).isEqualTo(0)
- assertThat(sliceUriWithoutParams.getSliceId()).isEqualTo("${entryId}_Bundle[{}]")
val sliceUriWithParams =
Uri.Builder().appendSpaParams(dest, entryId, bundleOf("p1" to "v1")).build()
assertThat(sliceUriWithParams.getEntryId()).isEqualTo(entryId)
- assertThat(sliceUriWithParams.getDestination()).isEqualTo(dest)
- assertThat(sliceUriWithParams.getRuntimeArguments().size()).isEqualTo(1)
- assertThat(sliceUriWithParams.getSliceId()).isEqualTo("${entryId}_Bundle[{p1=v1}]")
- }
-
- @Test
- fun createBroadcastPendingIntentTest() {
- SpaEnvironmentFactory.reset(spaEnvironment)
-
- // Empty Slice Uri
- assertThat(Uri.EMPTY.createBroadcastPendingIntent()).isNull()
-
- // Valid Slice Uri
- val dest = "myRoute"
- val entryId = "myEntry"
- val sliceUriWithoutParams = Uri.Builder().appendSpaParams(dest, entryId).build()
- val pendingIntent = sliceUriWithoutParams.createBroadcastPendingIntent()
- assertThat(pendingIntent).isNotNull()
- assertThat(pendingIntent!!.isBroadcast).isTrue()
- assertThat(pendingIntent.isImmutable).isFalse()
- }
-
- @Test
- fun createBrowsePendingIntentTest() {
- SpaEnvironmentFactory.reset(spaEnvironment)
-
- // Empty Slice Uri
- assertThat(Uri.EMPTY.createBrowsePendingIntent()).isNull()
-
- // Empty Intent
- assertThat(Intent().createBrowsePendingIntent()).isNull()
-
- // Valid Slice Uri
- val dest = "myRoute"
- val entryId = "myEntry"
- val sliceUri = Uri.Builder().appendSpaParams(dest, entryId).build()
- val pendingIntent = sliceUri.createBrowsePendingIntent()
- assertThat(pendingIntent).isNotNull()
- assertThat(pendingIntent!!.isActivity).isTrue()
- assertThat(pendingIntent.isImmutable).isTrue()
-
- // Valid Intent
- val intent = Intent().apply {
- putExtra("spaActivityDestination", dest)
- putExtra("highlightEntry", entryId)
- }
- val pendingIntent2 = intent.createBrowsePendingIntent()
- assertThat(pendingIntent2).isNotNull()
- assertThat(pendingIntent2!!.isActivity).isTrue()
- assertThat(pendingIntent2.isImmutable).isTrue()
}
}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
index 22a5ca3..4f8fd79 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
@@ -216,8 +216,6 @@
context: Context,
rootPages: List<SettingsPage> = emptyList(),
override val browseActivityClass: Class<out Activity>? = BlankActivity::class.java,
- override val sliceBroadcastReceiverClass: Class<out BroadcastReceiver>? =
- BlankSliceBroadcastReceiver::class.java,
override val logger: SpaLogger = object : SpaLogger {}
) : SpaEnvironment(context) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
index ef94526..26b5741 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
@@ -17,6 +17,7 @@
package com.android.settingslib.notification.data.repository
import android.app.NotificationManager
+import android.app.NotificationManager.EXTRA_NOTIFICATION_POLICY
import android.content.BroadcastReceiver
import android.content.ContentResolver
import android.content.Context
@@ -74,7 +75,7 @@
val receiver =
object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
- intent?.action?.let { action -> launch { send(action) } }
+ intent?.let { launch { send(it) } }
}
}
@@ -112,7 +113,9 @@
override val consolidatedNotificationPolicy: StateFlow<NotificationManager.Policy?> by lazy {
if (Flags.volumePanelBroadcastFix() && android.app.Flags.modesApi())
flowFromBroadcast(NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED) {
- notificationManager.consolidatedNotificationPolicy
+ // If available, get the value from extras to avoid a potential binder call.
+ it?.extras?.getParcelable(EXTRA_NOTIFICATION_POLICY)
+ ?: notificationManager.consolidatedNotificationPolicy
}
else
flowFromBroadcast(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED) {
@@ -126,11 +129,11 @@
}
}
- private fun <T> flowFromBroadcast(intentAction: String, mapper: () -> T) =
+ private fun <T> flowFromBroadcast(intentAction: String, mapper: (Intent?) -> T) =
notificationBroadcasts
- .filter { intentAction == it }
- .map { mapper() }
- .onStart { emit(mapper()) }
+ .filter { intentAction == it.action }
+ .map { mapper(it) }
+ .onStart { emit(mapper(null)) }
.flowOn(backgroundCoroutineContext)
.stateIn(scope, SharingStarted.WhileSubscribed(), null)
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
index 6e11e1f..4bd5cc4 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
@@ -22,6 +22,7 @@
import android.content.Context
import android.content.Intent
import android.database.ContentObserver
+import android.os.Parcelable
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.provider.Settings.Global
@@ -126,6 +127,26 @@
}
}
+ @EnableFlags(android.app.Flags.FLAG_MODES_API, Flags.FLAG_VOLUME_PANEL_BROADCAST_FIX)
+ @Test
+ fun consolidatedPolicyChanges_repositoryEmitsFromExtras() {
+ testScope.runTest {
+ val values = mutableListOf<NotificationManager.Policy?>()
+ `when`(notificationManager.consolidatedNotificationPolicy).thenReturn(testPolicy1)
+ underTest.consolidatedNotificationPolicy
+ .onEach { values.add(it) }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ triggerIntent(
+ NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED,
+ extras = mapOf(NotificationManager.EXTRA_NOTIFICATION_POLICY to testPolicy2))
+ runCurrent()
+
+ assertThat(values).containsExactly(null, testPolicy1, testPolicy2).inOrder()
+ }
+ }
+
@Test
fun zenModeChanges_repositoryEmits() {
testScope.runTest {
@@ -174,9 +195,13 @@
}
}
- private fun triggerIntent(action: String) {
+ private fun triggerIntent(action: String, extras: Map<String, Parcelable>? = null) {
verify(context).registerReceiver(receiverCaptor.capture(), any(), any(), any())
- receiverCaptor.value.onReceive(context, Intent(action))
+ val intent = Intent(action)
+ if (extras?.isNotEmpty() == true) {
+ extras.forEach { (key, value) -> intent.putExtra(key, value) }
+ }
+ receiverCaptor.value.onReceive(context, intent)
}
private fun triggerZenModeSettingUpdate() {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 8f247f6..bd4710b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -40,6 +40,7 @@
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusable
+import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
@@ -64,6 +65,7 @@
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
+import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
@@ -963,9 +965,25 @@
val selectedKey by viewModel.selectedKey.collectAsStateWithLifecycle()
val selectedIndex =
selectedKey?.let { key -> contentListState.list.indexOfFirst { it.key == key } }
+
+ val isSelected = selectedKey == model.key
+
+ val selectableModifier =
+ if (viewModel.isEditMode) {
+ Modifier.selectable(
+ selected = isSelected,
+ onClick = { viewModel.setSelectedKey(model.key) },
+ interactionSource = remember { MutableInteractionSource() },
+ indication = null,
+ )
+ } else {
+ Modifier
+ }
+
Box(
modifier =
modifier
+ .then(selectableModifier)
.thenIf(!viewModel.isEditMode && model.inQuietMode) {
Modifier.pointerInput(Unit) {
// consume tap to prevent the child view from triggering interactions with
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt
index 1ea73e1..620892a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalPopupSection.kt
@@ -23,6 +23,8 @@
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -39,11 +41,16 @@
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
@@ -54,6 +61,7 @@
import com.android.systemui.communal.ui.viewmodel.PopupType
import com.android.systemui.res.R
import javax.inject.Inject
+import kotlinx.coroutines.delay
class CommunalPopupSection
@Inject
@@ -91,6 +99,17 @@
onClick: () -> Unit,
onDismissRequest: () -> Unit,
) {
+ val interactionSource = remember { MutableInteractionSource() }
+ val focusRequester = remember { FocusRequester() }
+
+ val context = LocalContext.current
+
+ LaunchedEffect(Unit) {
+ // Adding a delay to ensure the animation completes before requesting focus
+ delay(250)
+ focusRequester.requestFocus()
+ }
+
Popup(
alignment = Alignment.TopCenter,
offset = IntOffset(0, 40),
@@ -100,6 +119,8 @@
Button(
modifier =
Modifier.height(56.dp)
+ .focusRequester(focusRequester)
+ .focusable(interactionSource = interactionSource)
.graphicsLayer { transformOrigin = TransformOrigin(0f, 0f) }
.animateEnterExit(
enter =
@@ -142,8 +163,7 @@
) {
Icon(
imageVector = Icons.Outlined.Widgets,
- contentDescription =
- stringResource(R.string.button_to_configure_widgets_text),
+ contentDescription = null,
tint = colors.onSecondary,
modifier = Modifier.size(20.dp)
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index a1f2042..859c036 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -90,10 +90,16 @@
*/
@Composable
fun SceneScope.Notifications(burnInParams: BurnInParameters?, modifier: Modifier = Modifier) {
+ val areNotificationsVisible by
+ lockscreenContentViewModel
+ .areNotificationsVisible(sceneKey)
+ .collectAsStateWithLifecycle(initialValue = false)
+ if (!areNotificationsVisible) {
+ return
+ }
+
val isShadeLayoutWide by
lockscreenContentViewModel.isShadeLayoutWide.collectAsStateWithLifecycle()
- val areNotificationsVisible by
- lockscreenContentViewModel.areNotificationsVisible.collectAsStateWithLifecycle()
val splitShadeTopMargin: Dp =
if (Flags.centralizedStatusBarHeightFix()) {
LargeScreenHeaderHelper.getLargeScreenHeaderHeight(LocalContext.current).dp
@@ -101,10 +107,6 @@
dimensionResource(id = R.dimen.large_screen_shade_header_height)
}
- if (!areNotificationsVisible) {
- return
- }
-
ConstrainedNotificationStack(
stackScrollView = stackScrollView.get(),
viewModel = viewModel,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index d95b388..20b1303 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -184,31 +184,33 @@
): Swipes {
val fromSource =
startedPosition?.let { position ->
- layoutImpl.swipeSourceDetector.source(
- fromScene.targetSize,
- position.round(),
- layoutImpl.density,
- orientation,
- )
+ layoutImpl.swipeSourceDetector
+ .source(
+ fromScene.targetSize,
+ position.round(),
+ layoutImpl.density,
+ orientation,
+ )
+ ?.resolve(layoutImpl.layoutDirection)
}
val upOrLeft =
- Swipe(
+ Swipe.Resolved(
direction =
when (orientation) {
- Orientation.Horizontal -> SwipeDirection.Left
- Orientation.Vertical -> SwipeDirection.Up
+ Orientation.Horizontal -> SwipeDirection.Resolved.Left
+ Orientation.Vertical -> SwipeDirection.Resolved.Up
},
pointerCount = pointersDown,
fromSource = fromSource,
)
val downOrRight =
- Swipe(
+ Swipe.Resolved(
direction =
when (orientation) {
- Orientation.Horizontal -> SwipeDirection.Right
- Orientation.Vertical -> SwipeDirection.Down
+ Orientation.Horizontal -> SwipeDirection.Resolved.Right
+ Orientation.Vertical -> SwipeDirection.Resolved.Down
},
pointerCount = pointersDown,
fromSource = fromSource,
@@ -833,10 +835,10 @@
/** The [Swipe] associated to a given fromScene, startedPosition and pointersDown. */
private class Swipes(
- val upOrLeft: Swipe?,
- val downOrRight: Swipe?,
- val upOrLeftNoSource: Swipe?,
- val downOrRightNoSource: Swipe?,
+ val upOrLeft: Swipe.Resolved?,
+ val downOrRight: Swipe.Resolved?,
+ val upOrLeftNoSource: Swipe.Resolved?,
+ val downOrRightNoSource: Swipe.Resolved?,
) {
/** The [UserActionResult] associated to up and down swipes. */
var upOrLeftResult: UserActionResult? = null
@@ -844,7 +846,7 @@
fun computeSwipesResults(fromScene: Scene): Pair<UserActionResult?, UserActionResult?> {
val userActions = fromScene.userActions
- fun result(swipe: Swipe?): UserActionResult? {
+ fun result(swipe: Swipe.Resolved?): UserActionResult? {
return userActions[swipe ?: return null]
}
@@ -940,25 +942,27 @@
when {
amount < 0f -> {
val actionUpOrLeft =
- Swipe(
+ Swipe.Resolved(
direction =
when (orientation) {
- Orientation.Horizontal -> SwipeDirection.Left
- Orientation.Vertical -> SwipeDirection.Up
+ Orientation.Horizontal -> SwipeDirection.Resolved.Left
+ Orientation.Vertical -> SwipeDirection.Resolved.Up
},
pointerCount = pointersInfo().pointersDown,
+ fromSource = null,
)
fromScene.userActions[actionUpOrLeft]
}
amount > 0f -> {
val actionDownOrRight =
- Swipe(
+ Swipe.Resolved(
direction =
when (orientation) {
- Orientation.Horizontal -> SwipeDirection.Right
- Orientation.Vertical -> SwipeDirection.Down
+ Orientation.Horizontal -> SwipeDirection.Resolved.Right
+ Orientation.Vertical -> SwipeDirection.Resolved.Down
},
pointerCount = pointersInfo().pointersDown,
+ fromSource = null,
)
fromScene.userActions[actionDownOrRight]
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
index b0dc3a1..97c0cef 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
@@ -21,14 +21,28 @@
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
/** The edge of a [SceneTransitionLayout]. */
-enum class Edge : SwipeSource {
- Left,
- Right,
- Top,
- Bottom,
+enum class Edge(private val resolveEdge: (LayoutDirection) -> Resolved) : SwipeSource {
+ Top(resolveEdge = { Resolved.Top }),
+ Bottom(resolveEdge = { Resolved.Bottom }),
+ Left(resolveEdge = { Resolved.Left }),
+ Right(resolveEdge = { Resolved.Right }),
+ Start(resolveEdge = { if (it == LayoutDirection.Ltr) Resolved.Left else Resolved.Right }),
+ End(resolveEdge = { if (it == LayoutDirection.Ltr) Resolved.Right else Resolved.Left });
+
+ override fun resolve(layoutDirection: LayoutDirection): Resolved {
+ return resolveEdge(layoutDirection)
+ }
+
+ enum class Resolved : SwipeSource.Resolved {
+ Left,
+ Right,
+ Top,
+ Bottom,
+ }
}
val DefaultEdgeDetector = FixedSizeEdgeDetector(40.dp)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index 936f4ba..a49f1af 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -37,7 +37,7 @@
val key: SceneKey,
layoutImpl: SceneTransitionLayoutImpl,
content: @Composable SceneScope.() -> Unit,
- actions: Map<UserAction, UserActionResult>,
+ actions: Map<UserAction.Resolved, UserActionResult>,
zIndex: Float,
) {
internal val scope = SceneScopeImpl(layoutImpl, this)
@@ -54,8 +54,8 @@
}
private fun checkValid(
- userActions: Map<UserAction, UserActionResult>
- ): Map<UserAction, UserActionResult> {
+ userActions: Map<UserAction.Resolved, UserActionResult>
+ ): Map<UserAction.Resolved, UserActionResult> {
userActions.forEach { (action, result) ->
if (key == result.toScene) {
error(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 45758c5..0c467b1 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -28,10 +28,13 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+import com.android.compose.animation.scene.UserAction.Resolved
/**
* [SceneTransitionLayout] is a container that automatically animates its content whenever its state
@@ -344,34 +347,71 @@
@Stable @ElementDsl interface MovableElementContentScope : BaseSceneScope, ElementBoxScope
/** An action performed by the user. */
-sealed interface UserAction {
+sealed class UserAction {
infix fun to(scene: SceneKey): Pair<UserAction, UserActionResult> {
return this to UserActionResult(toScene = scene)
}
+
+ /** Resolve this into a [Resolved] user action given [layoutDirection]. */
+ internal abstract fun resolve(layoutDirection: LayoutDirection): Resolved
+
+ /** A resolved [UserAction] that does not depend on the layout direction. */
+ internal sealed class Resolved
}
/** The user navigated back, either using a gesture or by triggering a KEYCODE_BACK event. */
-data object Back : UserAction
+data object Back : UserAction() {
+ override fun resolve(layoutDirection: LayoutDirection): Resolved = Resolved
+
+ internal object Resolved : UserAction.Resolved()
+}
/** The user swiped on the container. */
data class Swipe(
val direction: SwipeDirection,
val pointerCount: Int = 1,
val fromSource: SwipeSource? = null,
-) : UserAction {
+) : UserAction() {
companion object {
val Left = Swipe(SwipeDirection.Left)
val Up = Swipe(SwipeDirection.Up)
val Right = Swipe(SwipeDirection.Right)
val Down = Swipe(SwipeDirection.Down)
+ val Start = Swipe(SwipeDirection.Start)
+ val End = Swipe(SwipeDirection.End)
}
+
+ override fun resolve(layoutDirection: LayoutDirection): UserAction.Resolved {
+ return Resolved(
+ direction = direction.resolve(layoutDirection),
+ pointerCount = pointerCount,
+ fromSource = fromSource?.resolve(layoutDirection),
+ )
+ }
+
+ /** A resolved [Swipe] that does not depend on the layout direction. */
+ internal data class Resolved(
+ val direction: SwipeDirection.Resolved,
+ val pointerCount: Int,
+ val fromSource: SwipeSource.Resolved?,
+ ) : UserAction.Resolved()
}
-enum class SwipeDirection(val orientation: Orientation) {
- Up(Orientation.Vertical),
- Down(Orientation.Vertical),
- Left(Orientation.Horizontal),
- Right(Orientation.Horizontal),
+enum class SwipeDirection(internal val resolve: (LayoutDirection) -> Resolved) {
+ Up(resolve = { Resolved.Up }),
+ Down(resolve = { Resolved.Down }),
+ Left(resolve = { Resolved.Left }),
+ Right(resolve = { Resolved.Right }),
+ Start(resolve = { if (it == LayoutDirection.Ltr) Resolved.Left else Resolved.Right }),
+ End(resolve = { if (it == LayoutDirection.Ltr) Resolved.Right else Resolved.Left });
+
+ /** A resolved [SwipeDirection] that does not depend on the layout direction. */
+ internal enum class Resolved(val orientation: Orientation) {
+ Up(Orientation.Vertical),
+ Down(Orientation.Vertical),
+ Left(Orientation.Horizontal),
+ Right(Orientation.Horizontal),
+ }
}
/**
@@ -386,6 +426,16 @@
override fun equals(other: Any?): Boolean
override fun hashCode(): Int
+
+ /** Resolve this into a [Resolved] swipe source given [layoutDirection]. */
+ fun resolve(layoutDirection: LayoutDirection): Resolved
+
+ /** A resolved [SwipeSource] that does not depend on the layout direction. */
+ interface Resolved {
+ override fun equals(other: Any?): Boolean
+
+ override fun hashCode(): Int
+ }
}
interface SwipeSourceDetector {
@@ -460,11 +510,13 @@
scenes: SceneTransitionLayoutScope.() -> Unit,
) {
val density = LocalDensity.current
+ val layoutDirection = LocalLayoutDirection.current
val coroutineScope = rememberCoroutineScope()
val layoutImpl = remember {
SceneTransitionLayoutImpl(
state = state as BaseSceneTransitionLayoutState,
density = density,
+ layoutDirection = layoutDirection,
swipeSourceDetector = swipeSourceDetector,
transitionInterceptionThreshold = transitionInterceptionThreshold,
builder = scenes,
@@ -475,7 +527,7 @@
// TODO(b/317014852): Move this into the SideEffect {} again once STLImpl.scenes is not a
// SnapshotStateMap anymore.
- layoutImpl.updateScenes(scenes)
+ layoutImpl.updateScenes(scenes, layoutDirection)
SideEffect {
if (state != layoutImpl.state) {
@@ -486,6 +538,7 @@
}
layoutImpl.density = density
+ layoutImpl.layoutDirection = layoutDirection
layoutImpl.swipeSourceDetector = swipeSourceDetector
layoutImpl.transitionInterceptionThreshold = transitionInterceptionThreshold
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 6095419..3e48c42 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -33,6 +33,7 @@
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastForEachReversed
import com.android.compose.ui.util.lerp
@@ -45,6 +46,7 @@
internal class SceneTransitionLayoutImpl(
internal val state: BaseSceneTransitionLayoutState,
internal var density: Density,
+ internal var layoutDirection: LayoutDirection,
internal var swipeSourceDetector: SwipeSourceDetector,
internal var transitionInterceptionThreshold: Float,
builder: SceneTransitionLayoutScope.() -> Unit,
@@ -114,7 +116,7 @@
private set
init {
- updateScenes(builder)
+ updateScenes(builder, layoutDirection)
// DraggableHandlerImpl must wait for the scenes to be initialized, in order to access the
// current scene (required for SwipeTransition).
@@ -147,7 +149,10 @@
return scenes[key] ?: error("Scene $key is not configured")
}
- internal fun updateScenes(builder: SceneTransitionLayoutScope.() -> Unit) {
+ internal fun updateScenes(
+ builder: SceneTransitionLayoutScope.() -> Unit,
+ layoutDirection: LayoutDirection,
+ ) {
// Keep a reference of the current scenes. After processing [builder], the scenes that were
// not configured will be removed.
val scenesToRemove = scenes.keys.toMutableSet()
@@ -163,11 +168,13 @@
) {
scenesToRemove.remove(key)
+ val resolvedUserActions =
+ userActions.mapKeys { it.key.resolve(layoutDirection) }
val scene = scenes[key]
if (scene != null) {
// Update an existing scene.
scene.content = content
- scene.userActions = userActions
+ scene.userActions = resolvedUserActions
scene.zIndex = zIndex
} else {
// New scene.
@@ -176,7 +183,7 @@
key,
this@SceneTransitionLayoutImpl,
content,
- userActions,
+ resolvedUserActions,
zIndex,
)
}
@@ -213,7 +220,7 @@
@Composable
private fun BackHandler() {
val targetSceneForBack =
- scene(state.transitionState.currentScene).userActions[Back]?.toScene
+ scene(state.transitionState.currentScene).userActions[Back.Resolved]?.toScene
PredictiveBackHandler(state, coroutineScope, targetSceneForBack)
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index 171e243..aeb6262 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -98,7 +98,9 @@
/** Whether swipe should be enabled in the given [orientation]. */
private fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean {
- return userActions.keys.any { it is Swipe && it.direction.orientation == orientation }
+ return userActions.keys.any {
+ it is Swipe.Resolved && it.direction.orientation == orientation
+ }
}
private fun startDragImmediately(startedPosition: Offset): Boolean {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
index aa8dc38..7daefd0 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
@@ -44,26 +44,26 @@
return value
}
- return when (edge) {
- Edge.Top ->
+ return when (edge.resolve(layoutImpl.layoutDirection)) {
+ Edge.Resolved.Top ->
if (startsOutsideLayoutBounds) {
Offset(value.x, -elementSize.height.toFloat())
} else {
Offset(value.x, 0f)
}
- Edge.Left ->
+ Edge.Resolved.Left ->
if (startsOutsideLayoutBounds) {
Offset(-elementSize.width.toFloat(), value.y)
} else {
Offset(0f, value.y)
}
- Edge.Bottom ->
+ Edge.Resolved.Bottom ->
if (startsOutsideLayoutBounds) {
Offset(value.x, sceneSize.height.toFloat())
} else {
Offset(value.x, (sceneSize.height - elementSize.height).toFloat())
}
- Edge.Right ->
+ Edge.Resolved.Right ->
if (startsOutsideLayoutBounds) {
Offset(sceneSize.width.toFloat(), value.y)
} else {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index ff83d4b..7a5a84e 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -25,6 +25,7 @@
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.Velocity
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.animation.scene.NestedScrollBehavior.DuringTransitionBetweenScenes
@@ -61,8 +62,24 @@
canChangeScene = { canChangeScene(it) },
)
- val mutableUserActionsA = mutableMapOf(Swipe.Up to SceneB, Swipe.Down to SceneC)
- val mutableUserActionsB = mutableMapOf(Swipe.Up to SceneC, Swipe.Down to SceneA)
+ var layoutDirection = LayoutDirection.Rtl
+ set(value) {
+ field = value
+ layoutImpl.updateScenes(scenesBuilder, layoutDirection)
+ }
+
+ var mutableUserActionsA = mapOf(Swipe.Up to SceneB, Swipe.Down to SceneC)
+ set(value) {
+ field = value
+ layoutImpl.updateScenes(scenesBuilder, layoutDirection)
+ }
+
+ var mutableUserActionsB = mapOf(Swipe.Up to SceneC, Swipe.Down to SceneA)
+ set(value) {
+ field = value
+ layoutImpl.updateScenes(scenesBuilder, layoutDirection)
+ }
+
private val scenesBuilder: SceneTransitionLayoutScope.() -> Unit = {
scene(
key = SceneA,
@@ -94,6 +111,7 @@
SceneTransitionLayoutImpl(
state = layoutState,
density = Density(1f),
+ layoutDirection = LayoutDirection.Ltr,
swipeSourceDetector = DefaultEdgeDetector,
transitionInterceptionThreshold = transitionInterceptionThreshold,
builder = scenesBuilder,
@@ -466,10 +484,8 @@
dragController1.onDragStopped(velocity = -velocityThreshold)
assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
- mutableUserActionsA.remove(Swipe.Up)
- mutableUserActionsA.remove(Swipe.Down)
- mutableUserActionsB.remove(Swipe.Up)
- mutableUserActionsB.remove(Swipe.Down)
+ mutableUserActionsA = emptyMap()
+ mutableUserActionsB = emptyMap()
// start accelaratedScroll and scroll over to B -> null
val dragController2 = onDragStartedImmediately()
@@ -495,7 +511,7 @@
val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
- mutableUserActionsA[Swipe.Up] = UserActionResult(SceneC)
+ mutableUserActionsA += Swipe.Up to UserActionResult(SceneC)
dragController1.onDragDelta(pixels = up(fractionOfScreen = 0.1f))
// target stays B even though UserActions changed
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.2f)
@@ -512,7 +528,7 @@
val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
- mutableUserActionsA[Swipe.Up] = UserActionResult(SceneC)
+ mutableUserActionsA += Swipe.Up to UserActionResult(SceneC)
dragController1.onDragDelta(pixels = up(fractionOfScreen = 0.1f))
dragController1.onDragStopped(velocity = down(fractionOfScreen = 0.1f))
@@ -1149,8 +1165,7 @@
overscroll(SceneA, Orientation.Vertical) { fade(TestElements.Foo) }
}
- mutableUserActionsA.clear()
- mutableUserActionsA[Swipe.Up] = UserActionResult(SceneB)
+ mutableUserActionsA = mapOf(Swipe.Up to UserActionResult(SceneB))
val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
val dragController = onDragStarted(startedPosition = middle, overSlop = down(1f))
@@ -1178,8 +1193,7 @@
overscroll(SceneA, Orientation.Vertical) { fade(TestElements.Foo) }
}
- mutableUserActionsA.clear()
- mutableUserActionsA[Swipe.Down] = UserActionResult(SceneC)
+ mutableUserActionsA = mapOf(Swipe.Down to UserActionResult(SceneC))
val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
val dragController = onDragStarted(startedPosition = middle, overSlop = up(1f))
@@ -1220,7 +1234,8 @@
@Test
fun requireFullDistanceSwipe() = runGestureTest {
- mutableUserActionsA[Swipe.Up] = UserActionResult(SceneB, requiresFullDistanceSwipe = true)
+ mutableUserActionsA +=
+ Swipe.Up to UserActionResult(SceneB, requiresFullDistanceSwipe = true)
val controller = onDragStarted(overSlop = up(fractionOfScreen = 0.9f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.9f)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index 25ea2ee..0766e00 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -23,11 +23,13 @@
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.assertPositionInRootIsEqualTo
@@ -37,10 +39,12 @@
import androidx.compose.ui.test.swipeWithVelocity
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.animation.scene.TestScenes.SceneA
import com.android.compose.animation.scene.TestScenes.SceneB
+import com.android.compose.animation.scene.TestScenes.SceneC
import com.android.compose.animation.scene.subjects.assertThat
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
@@ -634,4 +638,152 @@
// Foo should be translated by (20dp, 30dp).
rule.onNode(isElement(TestElements.Foo)).assertPositionInRootIsEqualTo(20.dp, 30.dp)
}
+
+ @Test
+ fun startEnd_ltrLayout() {
+ val state =
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ initialScene = SceneA,
+ transitions =
+ transitions {
+ from(SceneA, to = SceneB) {
+ // We go to B by swiping to the start (left in LTR), so we make
+ // scene B appear from the end (right) edge.
+ translate(SceneB.rootElementKey, Edge.End)
+ }
+
+ from(SceneA, to = SceneC) {
+ // We go to C by swiping to the end (right in LTR), so we make
+ // scene C appear from the start (left) edge.
+ translate(SceneC.rootElementKey, Edge.Start)
+ }
+ },
+ )
+ }
+
+ val layoutSize = 200.dp
+ var touchSlop = 0f
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ SceneTransitionLayout(state, Modifier.size(layoutSize)) {
+ scene(SceneA, userActions = mapOf(Swipe.Start to SceneB, Swipe.End to SceneC)) {
+ Box(Modifier.fillMaxSize())
+ }
+ scene(SceneB) { Box(Modifier.element(SceneB.rootElementKey).fillMaxSize()) }
+ scene(SceneC) { Box(Modifier.element(SceneC.rootElementKey).fillMaxSize()) }
+ }
+ }
+
+ // Swipe to the left (start).
+ rule.onRoot().performTouchInput {
+ val middle = (layoutSize / 2).toPx()
+ down(Offset(middle, middle))
+ moveBy(Offset(-touchSlop, 0f), delayMillis = 1_000)
+ }
+
+ // Scene B should come from the right (end) edge.
+ var transition = assertThat(state.transitionState).isTransition()
+ assertThat(transition).hasFromScene(SceneA)
+ assertThat(transition).hasToScene(SceneB)
+ rule
+ .onNode(isElement(SceneB.rootElementKey))
+ .assertPositionInRootIsEqualTo(layoutSize, 0.dp)
+
+ // Release to go back to A.
+ rule.onRoot().performTouchInput { up() }
+ rule.waitForIdle()
+ assertThat(state.transitionState).isIdle()
+ assertThat(state.transitionState).hasCurrentScene(SceneA)
+
+ // Swipe to the right (end).
+ rule.onRoot().performTouchInput {
+ val middle = (layoutSize / 2).toPx()
+ down(Offset(middle, middle))
+ moveBy(Offset(touchSlop, 0f), delayMillis = 1_000)
+ }
+
+ // Scene C should come from the left (start) edge.
+ transition = assertThat(state.transitionState).isTransition()
+ assertThat(transition).hasFromScene(SceneA)
+ assertThat(transition).hasToScene(SceneC)
+ rule
+ .onNode(isElement(SceneC.rootElementKey))
+ .assertPositionInRootIsEqualTo(-layoutSize, 0.dp)
+ }
+
+ @Test
+ fun startEnd_rtlLayout() {
+ val state =
+ rule.runOnUiThread {
+ MutableSceneTransitionLayoutState(
+ initialScene = SceneA,
+ transitions =
+ transitions {
+ from(SceneA, to = SceneB) {
+ // We go to B by swiping to the start (right in RTL), so we make
+ // scene B appear from the end (left) edge.
+ translate(SceneB.rootElementKey, Edge.End)
+ }
+
+ from(SceneA, to = SceneC) {
+ // We go to C by swiping to the end (left in RTL), so we make
+ // scene C appear from the start (right) edge.
+ translate(SceneC.rootElementKey, Edge.Start)
+ }
+ },
+ )
+ }
+
+ val layoutSize = 200.dp
+ var touchSlop = 0f
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+ SceneTransitionLayout(state, Modifier.size(layoutSize)) {
+ scene(SceneA, userActions = mapOf(Swipe.Start to SceneB, Swipe.End to SceneC)) {
+ Box(Modifier.fillMaxSize())
+ }
+ scene(SceneB) { Box(Modifier.element(SceneB.rootElementKey).fillMaxSize()) }
+ scene(SceneC) { Box(Modifier.element(SceneC.rootElementKey).fillMaxSize()) }
+ }
+ }
+ }
+
+ // Swipe to the left (end).
+ rule.onRoot().performTouchInput {
+ val middle = (layoutSize / 2).toPx()
+ down(Offset(middle, middle))
+ moveBy(Offset(-touchSlop, 0f), delayMillis = 1_000)
+ }
+
+ // Scene C should come from the right (start) edge.
+ var transition = assertThat(state.transitionState).isTransition()
+ assertThat(transition).hasFromScene(SceneA)
+ assertThat(transition).hasToScene(SceneC)
+ rule
+ .onNode(isElement(SceneC.rootElementKey))
+ .assertPositionInRootIsEqualTo(layoutSize, 0.dp)
+
+ // Release to go back to A.
+ rule.onRoot().performTouchInput { up() }
+ rule.waitForIdle()
+ assertThat(state.transitionState).isIdle()
+ assertThat(state.transitionState).hasCurrentScene(SceneA)
+
+ // Swipe to the right (start).
+ rule.onRoot().performTouchInput {
+ val middle = (layoutSize / 2).toPx()
+ down(Offset(middle, middle))
+ moveBy(Offset(touchSlop, 0f), delayMillis = 1_000)
+ }
+
+ // Scene C should come from the left (end) edge.
+ transition = assertThat(state.transitionState).isTransition()
+ assertThat(transition).hasFromScene(SceneA)
+ assertThat(transition).hasToScene(SceneB)
+ rule
+ .onNode(isElement(SceneB.rootElementKey))
+ .assertPositionInRootIsEqualTo(-layoutSize, 0.dp)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index 0de0369..cee8ae9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -98,7 +98,7 @@
}
@Test
- fun keyguardGoesAway_forceBlankScene() =
+ fun keyguardGoesAway_whenLaunchingWidget_doNotForceBlankScene() =
with(kosmos) {
testScope.runTest {
val scene by collectLastValue(communalSceneInteractor.currentScene)
@@ -106,6 +106,27 @@
communalSceneInteractor.changeScene(CommunalScenes.Communal)
assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ communalSceneInteractor.setIsLaunchingWidget(true)
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ testScope = this
+ )
+
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ }
+ }
+
+ @Test
+ fun keyguardGoesAway_whenNotLaunchingWidget_forceBlankScene() =
+ with(kosmos) {
+ testScope.runTest {
+ val scene by collectLastValue(communalSceneInteractor.currentScene)
+
+ communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ communalSceneInteractor.setIsLaunchingWidget(false)
fakeKeyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.GONE,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt
index 0cd3fb2..d51d356 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt
@@ -26,14 +26,23 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.widgets.CommunalTransitionAnimatorController
import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.isNull
import org.mockito.kotlin.mock
-import org.mockito.kotlin.notNull
import org.mockito.kotlin.refEq
import org.mockito.kotlin.verify
@@ -41,6 +50,7 @@
@RunWith(AndroidJUnit4::class)
class SmartspaceInteractionHandlerTest : SysuiTestCase() {
private val activityStarter = mock<ActivityStarter>()
+ private val kosmos = testKosmos()
private val testIntent =
PendingIntent.getActivity(
@@ -51,29 +61,43 @@
)
private val testResponse = RemoteResponse.fromPendingIntent(testIntent)
- private val underTest: SmartspaceInteractionHandler by lazy {
- SmartspaceInteractionHandler(activityStarter)
+ private lateinit var underTest: SmartspaceInteractionHandler
+
+ @Before
+ fun setUp() {
+ with(kosmos) {
+ underTest = SmartspaceInteractionHandler(activityStarter, communalSceneInteractor)
+ }
}
@Test
fun launchAnimatorIsUsedForSmartspaceView() {
- val parent = FrameLayout(context)
- val view = SmartspaceAppWidgetHostView(context)
- parent.addView(view)
- val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view)
+ with(kosmos) {
+ testScope.runTest {
+ val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget)
+ assertFalse(launching!!)
- underTest.onInteraction(view, testIntent, testResponse)
+ val parent = FrameLayout(context)
+ val view = SmartspaceAppWidgetHostView(context)
+ parent.addView(view)
+ val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view)
- // Verify that we pass in a non-null animation controller
- verify(activityStarter)
- .startPendingIntentWithoutDismissing(
- /* intent = */ eq(testIntent),
- /* dismissShade = */ eq(false),
- /* intentSentUiThreadCallback = */ isNull(),
- /* animationController = */ notNull(),
- /* fillInIntent = */ refEq(fillInIntent),
- /* extraOptions = */ refEq(activityOptions.toBundle()),
- )
+ underTest.onInteraction(view, testIntent, testResponse)
+
+ // Verify that we set the state correctly
+ assertTrue(launching!!)
+ // Verify that we pass in a non-null Communal animation controller
+ verify(activityStarter)
+ .startPendingIntentWithoutDismissing(
+ /* intent = */ eq(testIntent),
+ /* dismissShade = */ eq(false),
+ /* intentSentUiThreadCallback = */ isNull(),
+ /* animationController = */ any<CommunalTransitionAnimatorController>(),
+ /* fillInIntent = */ refEq(fillInIntent),
+ /* extraOptions = */ refEq(activityOptions.toBundle()),
+ )
+ }
+ }
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt
new file mode 100644
index 0000000..ac50db4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt
@@ -0,0 +1,115 @@
+/*
+ * 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.
+ */
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.widgets.CommunalTransitionAnimatorController
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalTransitionAnimatorControllerTest : SysuiTestCase() {
+ private val controller = mock<ActivityTransitionAnimator.Controller>()
+ private val kosmos = testKosmos()
+
+ private lateinit var underTest: CommunalTransitionAnimatorController
+
+ @Before
+ fun setUp() {
+ with(kosmos) {
+ underTest = CommunalTransitionAnimatorController(controller, communalSceneInteractor)
+ }
+ }
+
+ @Test
+ fun doNotAnimate_launchingWidgetStateIsCleared() {
+ with(kosmos) {
+ testScope.runTest {
+ val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget)
+
+ communalSceneInteractor.setIsLaunchingWidget(true)
+ assertTrue(launching!!)
+
+ underTest.onIntentStarted(willAnimate = false)
+ assertFalse(launching!!)
+ verify(controller).onIntentStarted(willAnimate = false)
+ }
+ }
+ }
+
+ @Test
+ fun animationCancelled_launchingWidgetStateIsClearedAndSceneIsNotChanged() {
+ with(kosmos) {
+ testScope.runTest {
+ val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget)
+ val scene by collectLastValue(communalSceneInteractor.currentScene)
+
+ communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ Truth.assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ communalSceneInteractor.setIsLaunchingWidget(true)
+ assertTrue(launching!!)
+
+ underTest.onIntentStarted(willAnimate = true)
+ assertTrue(launching!!)
+ verify(controller).onIntentStarted(willAnimate = true)
+
+ underTest.onTransitionAnimationCancelled(newKeyguardOccludedState = true)
+ assertFalse(launching!!)
+ Truth.assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ verify(controller).onTransitionAnimationCancelled(newKeyguardOccludedState = true)
+ }
+ }
+ }
+
+ @Test
+ fun animationComplete_launchingWidgetStateIsClearedAndSceneIsChanged() {
+ with(kosmos) {
+ testScope.runTest {
+ val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget)
+ val scene by collectLastValue(communalSceneInteractor.currentScene)
+
+ communalSceneInteractor.changeScene(CommunalScenes.Communal)
+ Truth.assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ communalSceneInteractor.setIsLaunchingWidget(true)
+ assertTrue(launching!!)
+
+ underTest.onIntentStarted(willAnimate = true)
+ assertTrue(launching!!)
+ verify(controller).onIntentStarted(willAnimate = true)
+
+ underTest.onTransitionAnimationEnd(isExpandingFullyAbove = true)
+ assertFalse(launching!!)
+ Truth.assertThat(scene).isEqualTo(CommunalScenes.Blank)
+ verify(controller).onTransitionAnimationEnd(isExpandingFullyAbove = true)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
index 7044895..ea8b5ab 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
@@ -26,13 +26,21 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.isNull
import org.mockito.kotlin.mock
-import org.mockito.kotlin.notNull
import org.mockito.kotlin.refEq
import org.mockito.kotlin.verify
@@ -40,6 +48,7 @@
@RunWith(AndroidJUnit4::class)
class WidgetInteractionHandlerTest : SysuiTestCase() {
private val activityStarter = mock<ActivityStarter>()
+ private val kosmos = testKosmos()
private val testIntent =
PendingIntent.getActivity(
@@ -50,30 +59,44 @@
)
private val testResponse = RemoteResponse.fromPendingIntent(testIntent)
- private val underTest: WidgetInteractionHandler by lazy {
- WidgetInteractionHandler(activityStarter)
+ private lateinit var underTest: WidgetInteractionHandler
+
+ @Before
+ fun setUp() {
+ with(kosmos) {
+ underTest = WidgetInteractionHandler(activityStarter, communalSceneInteractor)
+ }
}
@Test
fun launchAnimatorIsUsedForWidgetView() {
- val parent = FrameLayout(context)
- val view = CommunalAppWidgetHostView(context)
- parent.addView(view)
- val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view)
+ with(kosmos) {
+ testScope.runTest {
+ val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget)
+ assertFalse(launching!!)
- underTest.onInteraction(view, testIntent, testResponse)
+ val parent = FrameLayout(context)
+ val view = CommunalAppWidgetHostView(context)
+ parent.addView(view)
+ val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view)
- // Verify that we pass in a non-null animation controller
- verify(activityStarter)
- .startPendingIntentMaybeDismissingKeyguard(
- /* intent = */ eq(testIntent),
- /* dismissShade = */ eq(false),
- /* intentSentUiThreadCallback = */ isNull(),
- /* animationController = */ notNull(),
- /* fillInIntent = */ refEq(fillInIntent),
- /* extraOptions = */ refEq(activityOptions.toBundle()),
- /* customMessage */ isNull(),
- )
+ underTest.onInteraction(view, testIntent, testResponse)
+
+ // Verify that we set the state correctly
+ assertTrue(launching!!)
+ // Verify that we pass in a non-null Communal animation controller
+ verify(activityStarter)
+ .startPendingIntentMaybeDismissingKeyguard(
+ /* intent = */ eq(testIntent),
+ /* dismissShade = */ eq(false),
+ /* intentSentUiThreadCallback = */ isNull(),
+ /* animationController = */ any<CommunalTransitionAnimatorController>(),
+ /* fillInIntent = */ refEq(fillInIntent),
+ /* extraOptions = */ refEq(activityOptions.toBundle()),
+ /* customMessage */ isNull(),
+ )
+ }
+ }
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
index 4a5342a..3a4b14b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
@@ -28,6 +28,8 @@
import com.android.systemui.shared.education.GestureType.BACK_GESTURE
import com.google.common.truth.Truth.assertThat
import java.io.File
+import java.time.Clock
+import java.time.Instant
import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.test.TestScope
@@ -48,6 +50,7 @@
private val dsScopeProvider: Provider<CoroutineScope> = Provider {
TestScope(kosmos.testDispatcher).backgroundScope
}
+ private val clock: Clock = FakeEduClock(Instant.ofEpochMilli(1000))
private val testUserId = 1111
// For deleting any test files created after the test
@@ -59,7 +62,7 @@
// needed before calling TemporaryFolder.newFolder().
val testContext = TestContext(context, tmpFolder.newFolder())
val userRepository = UserContextualEducationRepository(testContext, dsScopeProvider)
- underTest = ContextualEducationRepository(userRepository)
+ underTest = ContextualEducationRepositoryImpl(clock, userRepository)
underTest.setUser(testUserId)
}
@@ -85,6 +88,15 @@
assertThat(model?.signalCount).isEqualTo(1)
}
+ @Test
+ fun dataAddedOnUpdateShortcutTriggerTime() =
+ testScope.runTest {
+ val model by collectLastValue(underTest.readGestureEduModelFlow(BACK_GESTURE))
+ assertThat(model?.lastShortcutTriggeredTime).isNull()
+ underTest.updateShortcutTriggerTime(BACK_GESTURE)
+ assertThat(model?.lastShortcutTriggeredTime).isEqualTo(clock.instant())
+ }
+
/** Test context which allows overriding getFilesDir path */
private class TestContext(context: Context, private val folder: File) :
SysuiTestableContext(context) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
index 50772ee..3075c54 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -37,6 +38,7 @@
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.testKosmos
import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
import com.android.systemui.util.mockito.whenever
@@ -124,7 +126,50 @@
fun areNotificationsVisible_splitShadeTrue_true() =
with(kosmos) {
testScope.runTest {
- val areNotificationsVisible by collectLastValue(underTest.areNotificationsVisible)
+ val areNotificationsVisible by
+ collectLastValue(underTest.areNotificationsVisible(Scenes.Lockscreen))
+ shadeRepository.setShadeLayoutWide(true)
+ fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
+
+ assertThat(areNotificationsVisible).isTrue()
+ }
+ }
+
+ @Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun areNotificationsVisible_dualShadeWideOnLockscreen_true() =
+ with(kosmos) {
+ testScope.runTest {
+ val areNotificationsVisible by
+ collectLastValue(underTest.areNotificationsVisible(Scenes.Lockscreen))
+ shadeRepository.setShadeLayoutWide(true)
+ fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
+
+ assertThat(areNotificationsVisible).isTrue()
+ }
+ }
+
+ @Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun areNotificationsVisible_dualShadeWideOnNotificationsShade_false() =
+ with(kosmos) {
+ testScope.runTest {
+ val areNotificationsVisible by
+ collectLastValue(underTest.areNotificationsVisible(Scenes.NotificationsShade))
+ shadeRepository.setShadeLayoutWide(true)
+ fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
+
+ assertThat(areNotificationsVisible).isFalse()
+ }
+ }
+
+ @Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun areNotificationsVisible_dualShadeWideOnQuickSettingsShade_true() =
+ with(kosmos) {
+ testScope.runTest {
+ val areNotificationsVisible by
+ collectLastValue(underTest.areNotificationsVisible(Scenes.QuickSettingsShade))
shadeRepository.setShadeLayoutWide(true)
fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
@@ -137,7 +182,8 @@
fun areNotificationsVisible_withSmallClock_true() =
with(kosmos) {
testScope.runTest {
- val areNotificationsVisible by collectLastValue(underTest.areNotificationsVisible)
+ val areNotificationsVisible by
+ collectLastValue(underTest.areNotificationsVisible(Scenes.Lockscreen))
fakeKeyguardClockRepository.setClockSize(ClockSize.SMALL)
assertThat(areNotificationsVisible).isTrue()
}
@@ -148,7 +194,8 @@
fun areNotificationsVisible_withLargeClock_false() =
with(kosmos) {
testScope.runTest {
- val areNotificationsVisible by collectLastValue(underTest.areNotificationsVisible)
+ val areNotificationsVisible by
+ collectLastValue(underTest.areNotificationsVisible(Scenes.Lockscreen))
fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
assertThat(areNotificationsVisible).isFalse()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
index ccd78ee..59e8ea6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
@@ -40,6 +40,7 @@
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction
import com.android.systemui.settings.UserTracker
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.data.repository.FakeShadeRepository
@@ -136,7 +137,9 @@
communalSceneInteractor = communalSceneInteractor,
)
`when`(userTracker.userHandle).thenReturn(UserHandle.OWNER)
+ `when`(communalSceneInteractor.isCommunalVisible).thenReturn(MutableStateFlow(false))
`when`(communalSceneInteractor.isIdleOnCommunal).thenReturn(MutableStateFlow(false))
+ `when`(communalSceneInteractor.isLaunchingWidget).thenReturn(MutableStateFlow(false))
}
@Test
@@ -335,6 +338,102 @@
)
}
+ @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
+ @Test
+ fun startPendingIntentDismissingKeyguard_transitionAnimator_animateCommunal() {
+ val parent = FrameLayout(context)
+ val view =
+ object : View(context), LaunchableView {
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {}
+ }
+ parent.addView(view)
+ val controller = ActivityTransitionAnimator.Controller.fromView(view)
+ val pendingIntent = mock(PendingIntent::class.java)
+ `when`(pendingIntent.isActivity).thenReturn(true)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
+ `when`(keyguardStateController.isOccluded).thenReturn(true)
+ `when`(communalSceneInteractor.isCommunalVisible).thenReturn(MutableStateFlow(true))
+ `when`(communalSceneInteractor.isLaunchingWidget).thenReturn(MutableStateFlow(true))
+ `when`(activityIntentHelper.wouldPendingLaunchResolverActivity(eq(pendingIntent), anyInt()))
+ .thenReturn(false)
+ `when`(activityIntentHelper.wouldPendingShowOverLockscreen(eq(pendingIntent), anyInt()))
+ .thenReturn(false)
+
+ underTest.startPendingIntentDismissingKeyguard(
+ intent = pendingIntent,
+ dismissShade = false,
+ animationController = controller,
+ showOverLockscreen = true,
+ skipLockscreenChecks = false
+ )
+ mainExecutor.runAllReady()
+
+ val actionCaptor = argumentCaptor<OnDismissAction>()
+ verify(statusBarKeyguardViewManager)
+ .dismissWithAction(actionCaptor.capture(), eq(null), anyBoolean(), eq(null))
+ actionCaptor.firstValue.onDismiss()
+ mainExecutor.runAllReady()
+
+ verify(activityTransitionAnimator)
+ .startPendingIntentWithAnimation(
+ nullable(ActivityTransitionAnimator.Controller::class.java),
+ eq(true),
+ nullable(String::class.java),
+ eq(false),
+ any(),
+ )
+ }
+
+ @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
+ @Test
+ fun startPendingIntentDismissingKeyguard_transitionAnimator_doNotAnimateCommunal() {
+ val parent = FrameLayout(context)
+ val view =
+ object : View(context), LaunchableView {
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {}
+ }
+ parent.addView(view)
+ val controller = ActivityTransitionAnimator.Controller.fromView(view)
+ val pendingIntent = mock(PendingIntent::class.java)
+ `when`(pendingIntent.isActivity).thenReturn(true)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
+ `when`(keyguardStateController.isOccluded).thenReturn(true)
+ `when`(communalSceneInteractor.isCommunalVisible).thenReturn(MutableStateFlow(true))
+ `when`(communalSceneInteractor.isLaunchingWidget).thenReturn(MutableStateFlow(true))
+ `when`(activityIntentHelper.wouldPendingLaunchResolverActivity(eq(pendingIntent), anyInt()))
+ .thenReturn(false)
+ `when`(activityIntentHelper.wouldPendingShowOverLockscreen(eq(pendingIntent), anyInt()))
+ .thenReturn(false)
+
+ underTest.startPendingIntentDismissingKeyguard(
+ intent = pendingIntent,
+ dismissShade = false,
+ animationController = controller,
+ showOverLockscreen = true,
+ skipLockscreenChecks = false
+ )
+ mainExecutor.runAllReady()
+
+ val actionCaptor = argumentCaptor<OnDismissAction>()
+ verify(statusBarKeyguardViewManager)
+ .dismissWithAction(actionCaptor.capture(), eq(null), anyBoolean(), eq(null))
+ actionCaptor.firstValue.onDismiss()
+ mainExecutor.runAllReady()
+
+ val runnableCaptor = argumentCaptor<Runnable>()
+ verify(statusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(runnableCaptor.capture())
+ runnableCaptor.firstValue.run()
+
+ verify(activityTransitionAnimator)
+ .startPendingIntentWithAnimation(
+ nullable(ActivityTransitionAnimator.Controller::class.java),
+ eq(false),
+ nullable(String::class.java),
+ eq(false),
+ any(),
+ )
+ }
+
@Test
fun startActivity_noUserHandleProvided_getUserHandle() {
val intent = mock(Intent::class.java)
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 7f7e634..30f23bf 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -287,7 +287,8 @@
<integer name="doze_small_icon_alpha">222</integer><!-- 87% of 0xff -->
<!-- Doze: Table that translates sensor values from the doze_brightness_sensor_type sensor
- to brightness values; -1 means keeping the current brightness. -->
+ to brightness values in the integer scale [1, 255]; -1 means keeping the current
+ brightness. -->
<integer-array name="config_doze_brightness_sensor_to_brightness">
<item>-1</item> <!-- 0: OFF -->
<item>2</item> <!-- 1: NIGHT -->
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index bde6f42..6b58c07 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -105,7 +105,13 @@
.mapLatest(::determineSceneAfterTransition)
.filterNotNull()
.onEach { (nextScene, nextTransition) ->
- communalSceneInteractor.changeScene(nextScene, nextTransition)
+ if (!communalSceneInteractor.isLaunchingWidget.value) {
+ // When launching a widget, we don't want to animate the scene change or the
+ // Communal Hub will reveal the wallpaper even though it shouldn't. Instead we
+ // snap to the new scene as part of the launch animation, once the activity
+ // launch is done, so we don't change scene here.
+ communalSceneInteractor.changeScene(nextScene, nextTransition)
+ }
}
.launchIn(applicationScope)
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 e3ef6bb..748c4fa 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
@@ -20,6 +20,7 @@
import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL
import android.content.IntentFilter
import android.content.pm.UserInfo
+import android.os.UserHandle
import android.provider.Settings
import com.android.systemui.Flags.communalHub
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -102,7 +103,10 @@
.broadcastFlow(
filter =
IntentFilter(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
- user = user.userHandle
+ // In COPE management mode, the restriction from the managed profile may
+ // propagate to the main profile. Therefore listen to this broadcast across
+ // all users and update the state each time it changes.
+ user = UserHandle.ALL,
)
.emitOnStart()
.map { devicePolicyManager.areKeyguardWidgetsAllowed(user.id) }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
index fd540c4..122f9647 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
@@ -48,6 +48,15 @@
@Application private val applicationScope: CoroutineScope,
private val communalSceneRepository: CommunalSceneRepository,
) {
+ val _isLaunchingWidget = MutableStateFlow(false)
+
+ /** Whether a widget launch is currently in progress. */
+ val isLaunchingWidget: StateFlow<Boolean> = _isLaunchingWidget.asStateFlow()
+
+ fun setIsLaunchingWidget(launching: Boolean) {
+ _isLaunchingWidget.value = launching
+ }
+
/**
* Asks for an asynchronous scene witch to [newScene], which will use the corresponding
* installed transition or the one specified by [transitionKey], if provided.
diff --git a/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt
index a88b777..4e3d3ff 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt
@@ -22,21 +22,25 @@
import android.view.View
import android.widget.RemoteViews
import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.util.InteractionHandlerDelegate
import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView
import com.android.systemui.plugins.ActivityStarter
import javax.inject.Inject
-/**
- * Handles interactions on smartspace elements on the hub.
- */
-class SmartspaceInteractionHandler @Inject constructor(
+/** Handles interactions on smartspace elements on the hub. */
+class SmartspaceInteractionHandler
+@Inject
+constructor(
private val activityStarter: ActivityStarter,
+ communalSceneInteractor: CommunalSceneInteractor,
) : RemoteViews.InteractionHandler {
- private val delegate = InteractionHandlerDelegate(
- findViewToAnimate = { view -> view is SmartspaceAppWidgetHostView },
- intentStarter = this::startIntent,
- )
+ private val delegate =
+ InteractionHandlerDelegate(
+ communalSceneInteractor,
+ findViewToAnimate = { view -> view is SmartspaceAppWidgetHostView },
+ intentStarter = this::startIntent,
+ )
override fun onInteraction(
view: View,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt b/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt
index 40b182d..51a5fcd 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt
@@ -24,17 +24,17 @@
import androidx.core.util.component1
import androidx.core.util.component2
import com.android.systemui.animation.ActivityTransitionAnimator
-
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.widgets.CommunalTransitionAnimatorController
/** A delegate that can be used to launch activities from [RemoteViews] */
class InteractionHandlerDelegate(
+ private val communalSceneInteractor: CommunalSceneInteractor,
private val findViewToAnimate: (View) -> Boolean,
private val intentStarter: IntentStarter,
) : RemoteViews.InteractionHandler {
- /**
- * Responsible for starting the pending intent for launching activities.
- */
+ /** Responsible for starting the pending intent for launching activities. */
fun interface IntentStarter {
fun startPendingIntent(
intent: PendingIntent,
@@ -57,7 +57,10 @@
// activities.
val hostView = getNearestParent(view)
val animationController =
- hostView?.let(ActivityTransitionAnimator.Controller::fromView)
+ hostView?.let(ActivityTransitionAnimator.Controller::fromView)?.let {
+ communalSceneInteractor.setIsLaunchingWidget(true)
+ CommunalTransitionAnimatorController(it, communalSceneInteractor)
+ }
val (fillInIntent, activityOptions) = launchOptions
intentStarter.startPendingIntent(
pendingIntent,
@@ -66,7 +69,6 @@
animationController
)
}
-
else -> RemoteViews.startPendingIntent(view, pendingIntent, launchOptions)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt
new file mode 100644
index 0000000..4efaf87
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.DelegateTransitionAnimatorController
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
+
+/**
+ * An [ActivityTransitionAnimator.Controller] that takes care of updating the state of the Communal
+ * Hub at the right time.
+ */
+class CommunalTransitionAnimatorController(
+ delegate: ActivityTransitionAnimator.Controller,
+ private val communalSceneInteractor: CommunalSceneInteractor,
+) : DelegateTransitionAnimatorController(delegate) {
+ override fun onIntentStarted(willAnimate: Boolean) {
+ if (!willAnimate) {
+ // Other callbacks won't happen, so reset the state here.
+ communalSceneInteractor.setIsLaunchingWidget(false)
+ }
+ delegate.onIntentStarted(willAnimate)
+ }
+
+ override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) {
+ communalSceneInteractor.setIsLaunchingWidget(false)
+ delegate.onTransitionAnimationCancelled(newKeyguardOccludedState)
+ }
+
+ override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+ communalSceneInteractor.snapToScene(CommunalScenes.Blank)
+ communalSceneInteractor.setIsLaunchingWidget(false)
+ delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
index 72f9180..519903e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
@@ -22,6 +22,7 @@
import android.view.View
import android.widget.RemoteViews
import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.util.InteractionHandlerDelegate
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.ActivityStarter
@@ -32,10 +33,12 @@
@Inject
constructor(
private val activityStarter: ActivityStarter,
+ private val communalSceneInteractor: CommunalSceneInteractor
) : RemoteViews.InteractionHandler {
private val delegate =
InteractionHandlerDelegate(
+ communalSceneInteractor,
findViewToAnimate = { view -> view is CommunalAppWidgetHostView },
intentStarter = this::startIntent,
)
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 7ae8409..e07b5c2 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -48,6 +48,7 @@
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
+import com.android.systemui.Flags;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.plugins.SensorManagerPlugin;
import com.android.systemui.statusbar.phone.DozeParameters;
@@ -426,7 +427,11 @@
}
if (!anyListening) {
- mSecureSettings.unregisterContentObserverSync(mSettingsObserver);
+ if (Flags.registerContentObserversAsync()) {
+ mSecureSettings.unregisterContentObserverAsync(mSettingsObserver);
+ } else {
+ mSecureSettings.unregisterContentObserverSync(mSettingsObserver);
+ }
} else if (!mSettingRegistered) {
for (TriggerSensor s : mTriggerSensors) {
s.registerSettingsObserver(mSettingsObserver);
@@ -750,8 +755,13 @@
public void registerSettingsObserver(ContentObserver settingsObserver) {
if (mConfigured && !TextUtils.isEmpty(mSetting)) {
- mSecureSettings.registerContentObserverForUserSync(
- mSetting, mSettingsObserver, UserHandle.USER_ALL);
+ if (Flags.registerContentObserversAsync()) {
+ mSecureSettings.registerContentObserverForUserAsync(
+ mSetting, mSettingsObserver, UserHandle.USER_ALL);
+ } else {
+ mSecureSettings.registerContentObserverForUserSync(
+ mSetting, mSettingsObserver, UserHandle.USER_ALL);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
index e2bcb6b..53b9261 100644
--- a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
@@ -17,8 +17,12 @@
package com.android.systemui.education.dagger
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.education.data.repository.ContextualEducationRepository
+import com.android.systemui.education.data.repository.ContextualEducationRepositoryImpl
+import dagger.Binds
import dagger.Module
import dagger.Provides
+import java.time.Clock
import javax.inject.Qualifier
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -26,8 +30,15 @@
@Module
interface ContextualEducationModule {
+ @Binds
+ fun bindContextualEducationRepository(
+ impl: ContextualEducationRepositoryImpl
+ ): ContextualEducationRepository
+
@Qualifier annotation class EduDataStoreScope
+ @Qualifier annotation class EduClock
+
companion object {
@EduDataStoreScope
@Provides
@@ -36,5 +47,11 @@
): CoroutineScope {
return CoroutineScope(bgDispatcher + SupervisorJob())
}
+
+ @EduClock
+ @Provides
+ fun provideEduClock(): Clock {
+ return Clock.systemUTC()
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt b/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
index af35e8c..9f6cb4d 100644
--- a/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
@@ -16,11 +16,14 @@
package com.android.systemui.education.data.model
+import java.time.Instant
+
/**
* Model to store education data related to each gesture (e.g. Back, Home, All Apps, Overview). Each
* gesture stores its own model separately.
*/
data class GestureEduModel(
- val signalCount: Int,
- val educationShownCount: Int,
+ val signalCount: Int = 0,
+ val educationShownCount: Int = 0,
+ val lastShortcutTriggeredTime: Instant? = null,
)
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt b/packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt
index c9dd833..248b7a5 100644
--- a/packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/data/repository/ContextualEducationRepository.kt
@@ -17,26 +17,50 @@
package com.android.systemui.education.data.repository
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.education.dagger.ContextualEducationModule.EduClock
+import com.android.systemui.education.data.model.GestureEduModel
import com.android.systemui.shared.education.GestureType
+import java.time.Clock
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Encapsulates the functions of ContextualEducationRepository. */
+interface ContextualEducationRepository {
+ fun setUser(userId: Int)
+
+ fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel>
+
+ suspend fun incrementSignalCount(gestureType: GestureType)
+
+ suspend fun updateShortcutTriggerTime(gestureType: GestureType)
+}
/**
* Provide methods to read and update on field level and allow setting datastore when user is
* changed
*/
@SysUISingleton
-class ContextualEducationRepository
+class ContextualEducationRepositoryImpl
@Inject
-constructor(private val userEduRepository: UserContextualEducationRepository) {
+constructor(
+ @EduClock private val clock: Clock,
+ private val userEduRepository: UserContextualEducationRepository
+) : ContextualEducationRepository {
/** To change data store when user is changed */
- fun setUser(userId: Int) = userEduRepository.setUser(userId)
+ override fun setUser(userId: Int) = userEduRepository.setUser(userId)
- fun readGestureEduModelFlow(gestureType: GestureType) =
+ override fun readGestureEduModelFlow(gestureType: GestureType) =
userEduRepository.readGestureEduModelFlow(gestureType)
- suspend fun incrementSignalCount(gestureType: GestureType) {
+ override suspend fun incrementSignalCount(gestureType: GestureType) {
userEduRepository.updateGestureEduModel(gestureType) {
it.copy(signalCount = it.signalCount + 1)
}
}
+
+ override suspend fun updateShortcutTriggerTime(gestureType: GestureType) {
+ userEduRepository.updateGestureEduModel(gestureType) {
+ it.copy(lastShortcutTriggeredTime = clock.instant())
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
index 229511a..b7fc773 100644
--- a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
@@ -18,16 +18,19 @@
import android.content.Context
import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.MutablePreferences
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.intPreferencesKey
+import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.preferencesDataStoreFile
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.education.dagger.ContextualEducationModule.EduDataStoreScope
import com.android.systemui.education.data.model.GestureEduModel
import com.android.systemui.shared.education.GestureType
+import java.time.Instant
import javax.inject.Inject
import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
@@ -55,6 +58,7 @@
companion object {
const val SIGNAL_COUNT_SUFFIX = "_SIGNAL_COUNT"
const val NUMBER_OF_EDU_SHOWN_SUFFIX = "_NUMBER_OF_EDU_SHOWN"
+ const val LAST_SHORTCUT_TRIGGERED_TIME_SUFFIX = "_LAST_SHORTCUT_TRIGGERED_TIME"
const val DATASTORE_DIR = "education/USER%s_ContextualEducation"
}
@@ -91,6 +95,10 @@
return GestureEduModel(
signalCount = preferences[getSignalCountKey(gestureType)] ?: 0,
educationShownCount = preferences[getEducationShownCountKey(gestureType)] ?: 0,
+ lastShortcutTriggeredTime =
+ preferences[getLastShortcutTriggeredTimeKey(gestureType)]?.let {
+ Instant.ofEpochMilli(it)
+ },
)
}
@@ -103,6 +111,11 @@
val updatedModel = transform(currentModel)
preferences[getSignalCountKey(gestureType)] = updatedModel.signalCount
preferences[getEducationShownCountKey(gestureType)] = updatedModel.educationShownCount
+ updateTimeByInstant(
+ preferences,
+ updatedModel.lastShortcutTriggeredTime,
+ getLastShortcutTriggeredTimeKey(gestureType)
+ )
}
}
@@ -111,4 +124,19 @@
private fun getEducationShownCountKey(gestureType: GestureType): Preferences.Key<Int> =
intPreferencesKey(gestureType.name + NUMBER_OF_EDU_SHOWN_SUFFIX)
+
+ private fun getLastShortcutTriggeredTimeKey(gestureType: GestureType): Preferences.Key<Long> =
+ longPreferencesKey(gestureType.name + LAST_SHORTCUT_TRIGGERED_TIME_SUFFIX)
+
+ private fun updateTimeByInstant(
+ preferences: MutablePreferences,
+ instant: Instant?,
+ key: Preferences.Key<Long>
+ ) {
+ if (instant != null) {
+ preferences[key] = instant.toEpochMilli()
+ } else {
+ preferences.remove(key)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt
index 754ed6c..1ee0368 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt
@@ -25,6 +25,7 @@
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
@@ -45,6 +46,13 @@
edge = Edge.create(from = DREAMING, to = AOD),
)
+ /** Lockscreen views alpha */
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.sharedFlow(
+ duration = 300.milliseconds,
+ onStep = { it },
+ )
+
val deviceEntryBackgroundViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 7c46807..350ceb4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -91,8 +91,9 @@
private val dozingToGoneTransitionViewModel: DozingToGoneTransitionViewModel,
private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel,
- private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
+ private val dreamingToAodTransitionViewModel: DreamingToAodTransitionViewModel,
private val dreamingToGoneTransitionViewModel: DreamingToGoneTransitionViewModel,
+ private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
private val glanceableHubToLockscreenTransitionViewModel:
GlanceableHubToLockscreenTransitionViewModel,
private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
@@ -243,6 +244,7 @@
dozingToGoneTransitionViewModel.lockscreenAlpha(viewState),
dozingToLockscreenTransitionViewModel.lockscreenAlpha,
dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState),
+ dreamingToAodTransitionViewModel.lockscreenAlpha,
dreamingToGoneTransitionViewModel.lockscreenAlpha,
dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index 3b337fc..4bfefda 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.content.res.Resources
+import com.android.compose.animation.scene.SceneKey
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.biometrics.AuthController
import com.android.systemui.dagger.SysUISingleton
@@ -26,14 +27,17 @@
import com.android.systemui.keyguard.shared.model.ClockSize
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -57,19 +61,6 @@
val isShadeLayoutWide: StateFlow<Boolean> = shadeInteractor.isShadeLayoutWide
- val areNotificationsVisible: StateFlow<Boolean> =
- combine(
- clockSize,
- shadeInteractor.isShadeLayoutWide,
- ) { clockSize, isShadeLayoutWide ->
- clockSize == ClockSize.SMALL || isShadeLayoutWide
- }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = false,
- )
-
/** Amount of horizontal translation that should be applied to elements in the scene. */
val unfoldTranslations: StateFlow<UnfoldTranslations> =
combine(
@@ -97,6 +88,25 @@
initialValue = true,
)
+ /**
+ * Returns a flow that indicates whether lockscreen notifications should be rendered in the
+ * given [sceneKey].
+ */
+ fun areNotificationsVisible(sceneKey: SceneKey): Flow<Boolean> {
+ // `Scenes.NotificationsShade` renders its own separate notifications stack, so when it's
+ // open we avoid rendering the lockscreen notifications stack.
+ if (sceneKey == Scenes.NotificationsShade) {
+ return flowOf(false)
+ }
+
+ return combine(
+ clockSize,
+ shadeInteractor.isShadeLayoutWide,
+ ) { clockSize, isShadeLayoutWide ->
+ clockSize == ClockSize.SMALL || isShadeLayoutWide
+ }
+ }
+
fun getSmartSpacePaddingTop(resources: Resources): Int {
return if (clockSize.value == ClockSize.LARGE) {
resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) +
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java
index d68b22b..4d6cf78 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java
@@ -29,7 +29,6 @@
import androidx.annotation.NonNull;
-import com.android.server.display.feature.flags.Flags;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.settings.UserTracker;
@@ -81,10 +80,17 @@
mAvailable = true;
synchronized (mListeners) {
if (mListeners.size() > 0) {
- mSecureSettings.unregisterContentObserverSync(mContentObserver);
- mSecureSettings.registerContentObserverForUserSync(
- Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
- false, mContentObserver, newUser);
+ if (com.android.systemui.Flags.registerContentObserversAsync()) {
+ mSecureSettings.unregisterContentObserverAsync(mContentObserver);
+ mSecureSettings.registerContentObserverForUserAsync(
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
+ false, mContentObserver, newUser);
+ } else {
+ mSecureSettings.unregisterContentObserverSync(mContentObserver);
+ mSecureSettings.registerContentObserverForUserSync(
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
+ false, mContentObserver, newUser);
+ }
}
}
}
@@ -98,9 +104,15 @@
if (!mListeners.contains(listener)) {
mListeners.add(listener);
if (mListeners.size() == 1) {
- mSecureSettings.registerContentObserverForUserSync(
- Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
- false, mContentObserver, mUserTracker.getUserId());
+ if (com.android.systemui.Flags.registerContentObserversAsync()) {
+ mSecureSettings.registerContentObserverForUserAsync(
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
+ false, mContentObserver, mUserTracker.getUserId());
+ } else {
+ mSecureSettings.registerContentObserverForUserSync(
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
+ false, mContentObserver, mUserTracker.getUserId());
+ }
}
}
}
@@ -110,7 +122,11 @@
public void removeCallback(@androidx.annotation.NonNull Listener listener) {
synchronized (mListeners) {
if (mListeners.remove(listener) && mListeners.size() == 0) {
- mSecureSettings.unregisterContentObserverSync(mContentObserver);
+ if (com.android.systemui.Flags.registerContentObserversAsync()) {
+ mSecureSettings.unregisterContentObserverAsync(mContentObserver);
+ } else {
+ mSecureSettings.unregisterContentObserverSync(mContentObserver);
+ }
}
}
}
@@ -139,7 +155,8 @@
@Override
public boolean isInUpgradeMode(Resources resources) {
- return Flags.evenDimmer() && resources.getBoolean(
+ return com.android.server.display.feature.flags.Flags.evenDimmer()
+ && resources.getBoolean(
com.android.internal.R.bool.config_evenDimmerEnabled);
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 49810762..8e53949 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -137,14 +137,24 @@
public void startObserving() {
if (!mObserving) {
mObserving = true;
- mSecureSettings.registerContentObserverForUserSync(
- BRIGHTNESS_MODE_URI,
- false, this, UserHandle.USER_ALL);
+ if (Flags.registerContentObserversAsync()) {
+ mSecureSettings.registerContentObserverForUserAsync(
+ BRIGHTNESS_MODE_URI,
+ false, this, UserHandle.USER_ALL);
+ } else {
+ mSecureSettings.registerContentObserverForUserSync(
+ BRIGHTNESS_MODE_URI,
+ false, this, UserHandle.USER_ALL);
+ }
}
}
public void stopObserving() {
- mSecureSettings.unregisterContentObserverSync(this);
+ if (Flags.registerContentObserversAsync()) {
+ mSecureSettings.unregisterContentObserverAsync(this);
+ } else {
+ mSecureSettings.unregisterContentObserverSync(this);
+ }
mObserving = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index da89eea..766c391 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -526,7 +526,7 @@
keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
mContext.getString(R.string.keyboard_shortcut_group_applications_assist),
assistIcon,
- KeyEvent.KEYCODE_UNKNOWN,
+ KeyEvent.KEYCODE_A,
KeyEvent.META_META_ON));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 6d76200..6f29f61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -369,10 +369,12 @@
private void releaseBiometricWakeLock() {
if (mWakeLock != null) {
+ Trace.beginSection("release wake-and-unlock");
mHandler.removeCallbacks(mReleaseBiometricWakeLockRunnable);
mLogger.i("releasing biometric wakelock");
mWakeLock.release();
mWakeLock = null;
+ Trace.endSection();
}
}
@@ -398,7 +400,7 @@
}
mWakeLock = mPowerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, BIOMETRIC_WAKE_LOCK_NAME);
- Trace.beginSection("acquiring wake-and-unlock");
+ Trace.beginSection("acquire wake-and-unlock");
mWakeLock.acquire();
Trace.endSection();
mLogger.i("biometric acquired, grabbing biometric wakelock");
@@ -412,14 +414,13 @@
public void onBiometricDetected(int userId, BiometricSourceType biometricSourceType,
boolean isStrongBiometric) {
Trace.beginSection("BiometricUnlockController#onBiometricDetected");
- if (mUpdateMonitor.isGoingToSleep()) {
- Trace.endSection();
- return;
+ if (!mUpdateMonitor.isGoingToSleep()) {
+ startWakeAndUnlock(
+ MODE_SHOW_BOUNCER,
+ BiometricUnlockSource.Companion.fromBiometricSourceType(biometricSourceType)
+ );
}
- startWakeAndUnlock(
- MODE_SHOW_BOUNCER,
- BiometricUnlockSource.Companion.fromBiometricSourceType(biometricSourceType)
- );
+ Trace.endSection();
}
@Override
@@ -451,6 +452,7 @@
} else {
mLogger.d("onBiometricUnlocked aborted by bypass controller");
}
+ Trace.endSection();
}
/**
@@ -479,6 +481,7 @@
@WakeAndUnlockMode int mode,
BiometricUnlockSource biometricUnlockSource
) {
+ Trace.beginSection("BiometricUnlockController#startWakeAndUnlock");
mLogger.logStartWakeAndUnlock(mode);
boolean wasDeviceInteractive = mUpdateMonitor.isDeviceInteractive();
mMode = mode;
@@ -501,9 +504,7 @@
"android.policy:BIOMETRIC"
);
}
- Trace.beginSection("release wake-and-unlock");
releaseBiometricWakeLock();
- Trace.endSection();
};
final boolean wakeInKeyguard = mMode == MODE_WAKE_AND_UNLOCK_FROM_DREAM
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index 97791ac..316e1f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -22,6 +22,7 @@
import android.hardware.biometrics.BiometricSourceType
import android.provider.Settings
import com.android.app.tracing.ListenersTracing.forEachTraced
+import com.android.app.tracing.coroutines.launch
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -159,7 +160,7 @@
}
fun listenForQsExpandedChange() =
- applicationScope.launch {
+ applicationScope.launch("listenForQsExpandedChange") {
shadeInteractorLazy.get().qsExpansion.map { it > 0f }.distinctUntilChanged()
.collect { isQsExpanded ->
val changed = qsExpanded != isQsExpanded
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
index bcb613f..de76b10d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
@@ -276,10 +276,11 @@
statusBarController
}
+ val isCommunalDismissLaunch = isCommunalWidgetLaunch() && !actuallyShowOverLockscreen
// If we animate, don't collapse the shade and defer the keyguard dismiss (in case we
// run the animation on the keyguard). The animation will take care of (instantly)
// collapsing the shade and hiding the keyguard once it is done.
- val collapse = dismissShade && !animate
+ val collapse = (dismissShade || isCommunalDismissLaunch) && !animate
val runnable = Runnable {
try {
activityTransitionAnimator.startPendingIntentWithAnimation(
@@ -338,8 +339,9 @@
postOnUiThread(delay = 0) {
executeRunnableDismissingKeyguard(
runnable = runnable,
- afterKeyguardGone = willLaunchResolverActivity,
dismissShade = collapse,
+ afterKeyguardGone = willLaunchResolverActivity,
+ deferred = isCommunalDismissLaunch,
willAnimateOnKeyguard = animate,
customMessage = customMessage,
)
@@ -461,7 +463,9 @@
override fun onDismiss(): Boolean {
if (runnable != null) {
if (
- keyguardStateController.isShowing && keyguardStateController.isOccluded
+ keyguardStateController.isShowing &&
+ keyguardStateController.isOccluded &&
+ !isCommunalWidgetLaunch()
) {
statusBarKeyguardViewManagerLazy
.get()
@@ -473,17 +477,10 @@
if (dismissShade) {
shadeControllerLazy.get().collapseShadeForActivityStart()
}
- if (communalHub()) {
- communalSceneInteractor.changeSceneForActivityStartOnDismissKeyguard()
- }
return deferred
}
override fun willRunAnimationOnKeyguard(): Boolean {
- if (communalHub() && communalSceneInteractor.isIdleOnCommunal.value) {
- // Override to false when launching activity over the hub that requires auth
- return false
- }
return willAnimateOnKeyguard
}
}
@@ -639,7 +636,8 @@
showOverLockscreen: Boolean,
): Boolean {
// TODO(b/294418322): always support launch animations when occluded.
- val ignoreOcclusion = showOverLockscreen && mediaLockscreenLaunchAnimation()
+ val ignoreOcclusion =
+ (showOverLockscreen && mediaLockscreenLaunchAnimation()) || isCommunalWidgetLaunch()
if (keyguardStateController.isOccluded && !ignoreOcclusion) {
return false
}
@@ -659,6 +657,12 @@
return shouldAnimateLaunch(isActivityIntent, false)
}
+ private fun isCommunalWidgetLaunch(): Boolean {
+ return communalHub() &&
+ communalSceneInteractor.isCommunalVisible.value &&
+ communalSceneInteractor.isLaunchingWidget.value
+ }
+
private fun postOnUiThread(delay: Int = 0, runnable: Runnable) {
mainExecutor.executeDelayed(runnable, delay.toLong())
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
index 025354b..848a6e6 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
@@ -16,12 +16,12 @@
package com.android.systemui.util.settings
import android.annotation.UserIdInt
+import android.annotation.WorkerThread
import android.content.ContentResolver
import android.database.ContentObserver
import android.net.Uri
import android.os.UserHandle
import android.provider.Settings.SettingNotFoundException
-import androidx.annotation.WorkerThread
import com.android.app.tracing.TraceUtils.trace
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.settings.SettingsProxy.Companion.parseFloat
@@ -67,6 +67,7 @@
} else userTracker.userId
}
+ @WorkerThread
override fun registerContentObserverSync(uri: Uri, settingsObserver: ContentObserver) {
registerContentObserverForUserSync(uri, settingsObserver, userId)
}
@@ -83,6 +84,7 @@
}
/** Convenience wrapper around [ContentResolver.registerContentObserver].' */
+ @WorkerThread
override fun registerContentObserverSync(
uri: Uri,
notifyForDescendants: Boolean,
@@ -120,6 +122,7 @@
*
* Implicitly calls [getUriFor] on the passed in name.
*/
+ @WorkerThread
fun registerContentObserverForUserSync(
name: String,
settingsObserver: ContentObserver,
@@ -160,6 +163,7 @@
}
/** Convenience wrapper around [ContentResolver.registerContentObserver] */
+ @WorkerThread
fun registerContentObserverForUserSync(
uri: Uri,
settingsObserver: ContentObserver,
@@ -222,6 +226,7 @@
*
* Implicitly calls [getUriFor] on the passed in name.
*/
+ @WorkerThread
fun registerContentObserverForUserSync(
name: String,
notifyForDescendants: Boolean,
@@ -281,6 +286,7 @@
}
/** Convenience wrapper around [ContentResolver.registerContentObserver] */
+ @WorkerThread
fun registerContentObserverForUserSync(
uri: Uri,
notifyForDescendants: Boolean,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index 10d07a0..5052a00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -35,8 +35,8 @@
import android.os.PowerManager;
import android.provider.Settings;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
@@ -53,6 +53,7 @@
import com.android.systemui.unfold.FoldAodAnimationController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.Assert;
import org.junit.Before;
@@ -114,6 +115,7 @@
.thenReturn(mFoldAodAnimationController);
when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
+ SecureSettings secureSettings = new FakeSettings();
mDozeParameters = new DozeParameters(
mContext,
mHandler,
@@ -132,7 +134,7 @@
mStatusBarStateController,
mUserTracker,
mDozeInteractor,
- new FakeSettings()
+ secureSettings
);
verify(mBatteryController).addCallback(mBatteryStateChangeCallback.capture());
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
new file mode 100644
index 0000000..f73f43d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.education.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import java.time.Instant
+
+var Kosmos.contextualEducationRepository: ContextualEducationRepository by
+ Kosmos.Fixture { FakeContextualEducationRepository(FakeEduClock(Instant.MIN)) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
new file mode 100644
index 0000000..5410882
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.education.data.repository
+
+import com.android.systemui.education.data.model.GestureEduModel
+import com.android.systemui.shared.education.GestureType
+import java.time.Clock
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeContextualEducationRepository(private val clock: Clock) : ContextualEducationRepository {
+
+ private val userGestureMap = mutableMapOf<Int, GestureEduModel>()
+ private val _gestureEduModels = MutableStateFlow(GestureEduModel())
+ private val gestureEduModelsFlow = _gestureEduModels.asStateFlow()
+
+ override fun setUser(userId: Int) {
+ if (!userGestureMap.contains(userId)) {
+ userGestureMap[userId] = GestureEduModel()
+ }
+ _gestureEduModels.value = userGestureMap[userId]!!
+ }
+
+ override fun readGestureEduModelFlow(gestureType: GestureType): Flow<GestureEduModel> {
+ return gestureEduModelsFlow
+ }
+
+ override suspend fun incrementSignalCount(gestureType: GestureType) {
+ _gestureEduModels.value =
+ GestureEduModel(
+ signalCount = _gestureEduModels.value.signalCount + 1,
+ )
+ }
+
+ override suspend fun updateShortcutTriggerTime(gestureType: GestureType) {
+ _gestureEduModels.value = GestureEduModel(lastShortcutTriggeredTime = clock.instant())
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeEduClock.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeEduClock.kt
new file mode 100644
index 0000000..513c143
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeEduClock.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.education.data.repository
+
+import java.time.Clock
+import java.time.Instant
+import java.time.ZoneId
+
+class FakeEduClock(private val base: Instant) : Clock() {
+ private val zone: ZoneId = ZoneId.of("UTC")
+
+ override fun instant(): Instant {
+ return base
+ }
+
+ override fun withZone(zoneId: ZoneId?): Clock {
+ return FakeEduClock(base)
+ }
+
+ override fun getZone(): ZoneId {
+ return zone
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModelKosmos.kt
new file mode 100644
index 0000000..b5f0b89
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModelKosmos.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+var Kosmos.dreamingToAodTransitionViewModel by Fixture {
+ DreamingToAodTransitionViewModel(
+ deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
+ animationFlow = keyguardTransitionAnimationFlow,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index 2567ffe..3c5baa5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -46,6 +46,7 @@
dozingToGoneTransitionViewModel = dozingToGoneTransitionViewModel,
dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel,
+ dreamingToAodTransitionViewModel = dreamingToAodTransitionViewModel,
dreamingToGoneTransitionViewModel = dreamingToGoneTransitionViewModel,
dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel,
diff --git a/services/core/java/com/android/server/ExplicitHealthCheckController.java b/services/core/java/com/android/server/ExplicitHealthCheckController.java
index 3d610d3..6a6aea4 100644
--- a/services/core/java/com/android/server/ExplicitHealthCheckController.java
+++ b/services/core/java/com/android/server/ExplicitHealthCheckController.java
@@ -15,6 +15,7 @@
*/
package com.android.server;
+import static android.crashrecovery.flags.Flags.refactorCrashrecovery;
import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_HEALTH_CHECK_PASSED_PACKAGE;
import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_REQUESTED_PACKAGES;
import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_SUPPORTED_PACKAGES;
@@ -41,7 +42,6 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
import java.util.Collection;
import java.util.Collections;
@@ -363,22 +363,34 @@
@GuardedBy("mLock")
@Nullable
private ServiceInfo getServiceInfoLocked() {
- final String packageName =
- mContext.getPackageManager().getServicesSystemSharedLibraryPackageName();
- if (packageName == null) {
- Slog.w(TAG, "no external services package!");
- return null;
- }
+ if (refactorCrashrecovery()) {
+ final Intent intent = new Intent(ExplicitHealthCheckService.SERVICE_INTERFACE);
+ final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA
+ | PackageManager.MATCH_SYSTEM_ONLY);
+ if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+ Slog.w(TAG, "No valid components found.");
+ return null;
+ }
+ return resolveInfo.serviceInfo;
+ } else {
+ final String packageName =
+ mContext.getPackageManager().getServicesSystemSharedLibraryPackageName();
+ if (packageName == null) {
+ Slog.w(TAG, "no external services package!");
+ return null;
+ }
- final Intent intent = new Intent(ExplicitHealthCheckService.SERVICE_INTERFACE);
- intent.setPackage(packageName);
- final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
- PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
- if (resolveInfo == null || resolveInfo.serviceInfo == null) {
- Slog.w(TAG, "No valid components found.");
- return null;
+ final Intent intent = new Intent(ExplicitHealthCheckService.SERVICE_INTERFACE);
+ intent.setPackage(packageName);
+ final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+ if (resolveInfo == null || resolveInfo.serviceInfo == null) {
+ Slog.w(TAG, "No valid components found.");
+ return null;
+ }
+ return resolveInfo.serviceInfo;
}
- return resolveInfo.serviceInfo;
}
@GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0d309eb..a84306b 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -739,6 +739,8 @@
// Broadcast receiver for device connections intent broadcasts
private final BroadcastReceiver mReceiver = new AudioServiceBroadcastReceiver();
+ private final Executor mAudioServerLifecycleExecutor;
+
private IMediaProjectionManager mProjectionService; // to validate projection token
/** Interface for UserManagerService. */
@@ -1059,7 +1061,8 @@
audioserverPermissions() ?
initializeAudioServerPermissionProvider(
context, audioPolicyFacade, audioserverLifecycleExecutor) :
- null
+ null,
+ audioserverLifecycleExecutor
);
}
@@ -1145,13 +1148,16 @@
* {@link AudioSystemThread} is created as the messaging thread instead.
* @param appOps {@link AppOpsManager} system service
* @param enforcer Used for permission enforcing
+ * @param permissionProvider Used to push permissions to audioserver
+ * @param audioserverLifecycleExecutor Used for tasks managing audioserver lifecycle
*/
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
public AudioService(Context context, AudioSystemAdapter audioSystem,
SystemServerAdapter systemServer, SettingsAdapter settings,
AudioVolumeGroupHelperBase audioVolumeGroupHelper, AudioPolicyFacade audioPolicy,
@Nullable Looper looper, AppOpsManager appOps, @NonNull PermissionEnforcer enforcer,
- /* @NonNull */ AudioServerPermissionProvider permissionProvider) {
+ /* @NonNull */ AudioServerPermissionProvider permissionProvider,
+ Executor audioserverLifecycleExecutor) {
super(enforcer);
sLifecycleLogger.enqueue(new EventLogger.StringEvent("AudioService()"));
mContext = context;
@@ -1159,6 +1165,7 @@
mAppOps = appOps;
mPermissionProvider = permissionProvider;
+ mAudioServerLifecycleExecutor = audioserverLifecycleExecutor;
mAudioSystem = audioSystem;
mSystemServer = systemServer;
@@ -1170,6 +1177,34 @@
mBroadcastHandlerThread = new HandlerThread("AudioService Broadcast");
mBroadcastHandlerThread.start();
+ // Listen to permission invalidations for the PermissionProvider
+ if (audioserverPermissions()) {
+ final Handler broadcastHandler = mBroadcastHandlerThread.getThreadHandler();
+ mAudioSystem.listenForSystemPropertyChange(PermissionManager.CACHE_KEY_PACKAGE_INFO,
+ new Runnable() {
+ // Roughly chosen to be long enough to suppress the autocork behavior
+ // of the permission cache (50ms), and longer than the task could reasonably
+ // take, even with many packages and users, while not introducing visible
+ // permission leaks - since the app needs to restart, and trigger an action
+ // which requires permissions from audioserver before this delay.
+ // For RECORD_AUDIO, we are additionally protected by appops.
+ final long UPDATE_DELAY_MS = 110;
+ final AtomicLong scheduledUpdateTimestamp = new AtomicLong(0);
+ @Override
+ public void run() {
+ var currentTime = SystemClock.uptimeMillis();
+ if (currentTime > scheduledUpdateTimestamp.get()) {
+ scheduledUpdateTimestamp.set(currentTime + UPDATE_DELAY_MS);
+ broadcastHandler.postAtTime( () ->
+ mAudioServerLifecycleExecutor.execute(mPermissionProvider
+ ::onPermissionStateChanged),
+ currentTime + UPDATE_DELAY_MS
+ );
+ }
+ }
+ });
+ }
+
mDeviceBroker = new AudioDeviceBroker(mContext, this, mAudioSystem);
mIsSingleVolume = AudioSystem.isSingleVolume(context);
@@ -11965,29 +12000,6 @@
provider.onServiceStart(audioPolicy.getPermissionController());
});
- // Set up event listeners
- // Must be kept in sync with PermissionManager
- Runnable cacheSysPropHandler = new Runnable() {
- private AtomicReference<SystemProperties.Handle> mHandle = new AtomicReference();
- private AtomicLong mNonce = new AtomicLong();
- @Override
- public void run() {
- if (mHandle.get() == null) {
- // Cache the handle
- mHandle.compareAndSet(null, SystemProperties.find(
- PermissionManager.CACHE_KEY_PACKAGE_INFO));
- }
- long nonce;
- SystemProperties.Handle ref;
- if ((ref = mHandle.get()) != null && (nonce = ref.getLong(0)) != 0 &&
- mNonce.getAndSet(nonce) != nonce) {
- audioserverExecutor.execute(() -> provider.onPermissionStateChanged());
- }
- }
- };
-
- SystemProperties.addChangeCallback(cacheSysPropHandler);
-
IntentFilter packageUpdateFilter = new IntentFilter();
packageUpdateFilter.addAction(ACTION_PACKAGE_ADDED);
packageUpdateFilter.addAction(ACTION_PACKAGE_REMOVED);
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 7f4bc74..d083c68 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -748,6 +748,10 @@
return AudioSystem.setMasterMute(mute);
}
+ public void listenForSystemPropertyChange(String systemPropertyName, Runnable callback) {
+ AudioSystem.listenForSystemPropertyChange(systemPropertyName, callback);
+ }
+
/**
* Part of AudioService dump
* @param pw
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index f5231ae..7a055d1 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -619,6 +619,15 @@
* </idleScreenRefreshRateTimeout>
* <supportsVrr>true</supportsVrr>
*
+ * <dozeBrightnessSensorValueToBrightness>
+ * <item>-1</item> <!-- 0: OFF -->
+ * <item>0.003937008</item> <!-- 1: NIGHT -->
+ * <item>0.015748031</item> <!-- 2: LOW -->
+ * <item>0.102362205</item> <!-- 3: HIGH -->
+ * <item>0.106299213</item> <!-- 4: SUN -->
+ * </dozeBrightnessSensorValueToBrightness>
+ * <defaultDozeBrightness>0.235</defaultDozeBrightness>
+ *
* </displayConfiguration>
* }
* </pre>
@@ -638,6 +647,10 @@
public static final int DEFAULT_LOW_REFRESH_RATE = 60;
+ // Float.NaN (used as invalid for brightness) cannot be stored in config.xml
+ // so -2 is used instead
+ public static final float INVALID_BRIGHTNESS_IN_CONFIG = -2f;
+
@VisibleForTesting
static final float BRIGHTNESS_DEFAULT = 0.5f;
private static final String ETC_DIR = "etc";
@@ -656,10 +669,6 @@
private static final int INTERPOLATION_DEFAULT = 0;
private static final int INTERPOLATION_LINEAR = 1;
- // Float.NaN (used as invalid for brightness) cannot be stored in config.xml
- // so -2 is used instead
- private static final float INVALID_BRIGHTNESS_IN_CONFIG = -2f;
-
// Length of the ambient light horizon used to calculate the long term estimate of ambient
// light.
private static final int AMBIENT_LIGHT_LONG_HORIZON_MILLIS = 10000;
@@ -670,6 +679,11 @@
// Invalid value of AutoBrightness brightening and darkening light debounce
private static final int INVALID_AUTO_BRIGHTNESS_LIGHT_DEBOUNCE = -1;
+ @VisibleForTesting
+ static final float HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT = 0.5f;
+
+ private static final int KEEP_CURRENT_BRIGHTNESS = -1;
+
private final Context mContext;
// The details of the ambient light sensor associated with this display.
@@ -877,6 +891,10 @@
private boolean mVrrSupportEnabled;
+ @Nullable
+ private float[] mDozeBrightnessSensorValueToBrightness;
+ private float mDefaultDozeBrightness;
+
private final DisplayManagerFlags mFlags;
@VisibleForTesting
@@ -1592,6 +1610,24 @@
return mVrrSupportEnabled;
}
+ /**
+ * While the device is dozing, a designated light sensor is used to determine the brightness.
+ * @return The mapping between doze brightness sensor values and brightness values. The value
+ * -1 means that the current brightness should be kept.
+ */
+ @Nullable
+ public float[] getDozeBrightnessSensorValueToBrightness() {
+ return mDozeBrightnessSensorValueToBrightness;
+ }
+
+ /**
+ * @return The default doze brightness to use while no other doze brightness is available. Can
+ * be {@link PowerManager#BRIGHTNESS_INVALID_FLOAT} if undefined.
+ */
+ public float getDefaultDozeBrightness() {
+ return mDefaultDozeBrightness;
+ }
+
@Override
public String toString() {
return "DisplayDeviceConfig{"
@@ -1689,6 +1725,9 @@
? mEvenDimmerBrightnessData.toString() : "null")
+ "\n"
+ "mVrrSupported= " + mVrrSupportEnabled + "\n"
+ + "mDozeBrightnessSensorValueToBrightness= "
+ + Arrays.toString(mDozeBrightnessSensorValueToBrightness) + "\n"
+ + "mDefaultDozeBrightness= " + mDefaultDozeBrightness + "\n"
+ "}";
}
@@ -1783,6 +1822,7 @@
loadBrightnessCapForWearBedtimeMode(config);
loadIdleScreenRefreshRateTimeoutConfigs(config);
mVrrSupportEnabled = config.getSupportsVrr();
+ loadDozeBrightness(config);
} else {
Slog.w(TAG, "DisplayDeviceConfig file is null");
}
@@ -1811,6 +1851,7 @@
loadRefreshRateSetting(null);
loadBrightnessCapForWearBedtimeModeFromConfigXml();
loadIdleScreenRefreshRateTimeoutConfigs(null);
+ loadDozeBrightness(null);
mLoadedFrom = "<config.xml>";
}
@@ -2745,6 +2786,37 @@
}
}
+ private void loadDozeBrightness(DisplayConfiguration config) {
+ if (mFlags.isDozeBrightnessFloatEnabled() && config != null
+ && config.getDozeBrightnessSensorValueToBrightness() != null) {
+ List<BigDecimal> values = config.getDozeBrightnessSensorValueToBrightness().getItem();
+ mDozeBrightnessSensorValueToBrightness = new float[values.size()];
+ for (int i = 0; i < values.size(); i++) {
+ float backlight = values.get(i).floatValue();
+ if (backlight != KEEP_CURRENT_BRIGHTNESS) {
+ mDozeBrightnessSensorValueToBrightness[i] =
+ getBrightnessFromBacklight(backlight);
+ } else {
+ mDozeBrightnessSensorValueToBrightness[i] = KEEP_CURRENT_BRIGHTNESS;
+ }
+ }
+ }
+
+ if (mFlags.isDozeBrightnessFloatEnabled() && config != null
+ && config.getDefaultDozeBrightness() != null) {
+ float backlight = config.getDefaultDozeBrightness().floatValue();
+ mDefaultDozeBrightness = getBrightnessFromBacklight(backlight);
+ } else {
+ mDefaultDozeBrightness = mContext.getResources().getFloat(
+ com.android.internal.R.dimen.config_screenBrightnessDozeFloat);
+ if (mDefaultDozeBrightness == INVALID_BRIGHTNESS_IN_CONFIG) {
+ mDefaultDozeBrightness = BrightnessSynchronizer.brightnessIntToFloat(
+ mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_screenBrightnessDoze));
+ }
+ }
+ }
+
private void validateIdleScreenRefreshRateTimeoutConfig(
IdleScreenRefreshRateTimeout idleScreenRefreshRateTimeoutConfig) {
IdleScreenRefreshRateTimeoutLuxThresholds idleScreenRefreshRateTimeoutLuxThresholds =
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 2f3584c..b3a6c1c 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -4726,6 +4726,32 @@
DisplayManagerService.this.mDisplayModeDirector.requestDisplayModes(
token, displayId, modeIds);
}
+
+ @EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+ @Override // Binder call
+ public float[] getDozeBrightnessSensorValueToBrightness(int displayId) {
+ getDozeBrightnessSensorValueToBrightness_enforcePermission();
+ DisplayDeviceConfig ddc =
+ mDisplayDeviceConfigProvider.getDisplayDeviceConfig(displayId);
+ if (ddc == null) {
+ throw new IllegalArgumentException(
+ "Display ID does not have a config: " + displayId);
+ }
+ return ddc.getDozeBrightnessSensorValueToBrightness();
+ }
+
+ @EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+ @Override // Binder call
+ public float getDefaultDozeBrightness(int displayId) {
+ getDefaultDozeBrightness_enforcePermission();
+ DisplayDeviceConfig ddc =
+ mDisplayDeviceConfigProvider.getDisplayDeviceConfig(displayId);
+ if (ddc == null) {
+ throw new IllegalArgumentException(
+ "Display ID does not have a config for doze-default: " + displayId);
+ }
+ return ddc.getDefaultDozeBrightness();
+ }
}
private static boolean isValidBrightness(float brightness) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 58309c2..5c1e783 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -272,7 +272,7 @@
private final SettingsObserver mSettingsObserver;
// The doze screen brightness.
- private final float mScreenBrightnessDozeConfig;
+ private float mScreenBrightnessDozeConfig;
// True if auto-brightness should be used.
private boolean mUseSoftwareAutoBrightnessConfig;
@@ -550,7 +550,7 @@
// DOZE AND DIM SETTINGS
mScreenBrightnessDozeConfig = BrightnessUtils.clampAbsoluteBrightness(
- pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE));
+ mDisplayDeviceConfig.getDefaultDozeBrightness());
loadBrightnessRampRates();
mSkipScreenOnBrightnessRamp = resources.getBoolean(
R.bool.config_skipScreenOnBrightnessRamp);
@@ -932,6 +932,8 @@
HighBrightnessModeMetadata hbmMetadata) {
// All properties that depend on the associated DisplayDevice and the DDC must be
// updated here.
+ mScreenBrightnessDozeConfig = BrightnessUtils.clampAbsoluteBrightness(
+ mDisplayDeviceConfig.getDefaultDozeBrightness());
loadBrightnessRampRates();
loadNitsRange(mContext.getResources());
setUpAutoBrightness(mContext, mHandler);
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 3ce7d2a..e1934b0 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -154,6 +154,10 @@
Flags::useFusionProxSensor
);
+ private final FlagState mDozeBrightnessFloat = new FlagState(
+ Flags.FLAG_DOZE_BRIGHTNESS_FLOAT,
+ Flags::dozeBrightnessFloat);
+
private final FlagState mOffloadControlsDozeAutoBrightness = new FlagState(
Flags.FLAG_OFFLOAD_CONTROLS_DOZE_AUTO_BRIGHTNESS,
Flags::offloadControlsDozeAutoBrightness
@@ -347,6 +351,10 @@
return mUseFusionProxSensor.getName();
}
+ public boolean isDozeBrightnessFloatEnabled() {
+ return mDozeBrightnessFloat.isEnabled();
+ }
+
/**
* @return Whether DisplayOffload should control auto-brightness in doze
*/
@@ -415,6 +423,7 @@
pw.println(" " + mRefactorDisplayPowerController);
pw.println(" " + mResolutionBackupRestore);
pw.println(" " + mUseFusionProxSensor);
+ pw.println(" " + mDozeBrightnessFloat);
pw.println(" " + mOffloadControlsDozeAutoBrightness);
pw.println(" " + mPeakRefreshRatePhysicalLimit);
pw.println(" " + mIgnoreAppPreferredRefreshRate);
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index fd3af23..ac5f97f 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -246,6 +246,14 @@
}
flag {
+ name: "doze_brightness_float"
+ namespace: "display_manager"
+ description: "Define doze brightness in the float scale [0, 1]."
+ bug: "343796384"
+ is_fixed_read_only: true
+}
+
+flag {
name: "offload_controls_doze_auto_brightness"
namespace: "display_manager"
description: "Allows the registered DisplayOffloader to control if auto-brightness is used in doze"
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
index 9968590..8ca0458 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
@@ -220,6 +220,11 @@
}
@AnyThread
+ static void onUserCreated(@UserIdInt int userId) {
+ sWriter.onUserCreated(userId);
+ }
+
+ @AnyThread
static void remove(@UserIdInt int userId, @NonNull Handler ioHandler) {
sWriter.onUserRemoved(userId);
ioHandler.post(() -> {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index e342c78..f5faeef 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -176,7 +176,6 @@
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
-import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.input.InputManagerInternal;
@@ -1013,7 +1012,9 @@
@Override
public void onUserCreated(UserInfo user, @Nullable Object token) {
// Called directly from UserManagerService. Do not block the calling thread.
- initializeUsersAsync(new int[user.id]);
+ final int userId = user.id;
+ AdditionalSubtypeMapRepository.onUserCreated(userId);
+ initializeUsersAsync(new int[userId]);
}
@Override
@@ -1390,9 +1391,7 @@
getPackageManagerForUser(mContext, currentUserId),
newSettings.getEnabledInputMethodList());
- final var unused = SystemServerInitThreadPool.submit(
- AdditionalSubtypeMapRepository::startWriterThread,
- "Start AdditionalSubtypeMapRepository's writer thread");
+ AdditionalSubtypeMapRepository.startWriterThread();
if (mConcurrentMultiUserModeEnabled) {
for (int userId : mUserManagerInternal.getUserIds()) {
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 56e4590..46585a5 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -80,6 +80,7 @@
import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.annotations.KeepForWeakReference;
import com.android.internal.content.PackageMonitor;
import com.android.internal.content.om.OverlayConfig;
import com.android.internal.util.ArrayUtils;
@@ -261,6 +262,7 @@
private final OverlayActorEnforcer mActorEnforcer;
+ @KeepForWeakReference
private final PackageMonitor mPackageMonitor = new OverlayManagerPackageMonitor();
private int mPrevStartedUserId = -1;
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index ce0120c..6fe1ccd 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -33,6 +33,7 @@
import static com.android.internal.util.LatencyTracker.ACTION_TURN_ON_SCREEN;
import static com.android.server.deviceidle.Flags.disableWakelocksInLightIdle;
+import static com.android.server.display.DisplayDeviceConfig.INVALID_BRIGHTNESS_IN_CONFIG;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -239,9 +240,6 @@
// This should perhaps be a setting.
private static final int SCREEN_BRIGHTNESS_BOOST_TIMEOUT = 5 * 1000;
- // Float.NaN cannot be stored in config.xml so -2 is used instead
- private static final float INVALID_BRIGHTNESS_IN_CONFIG = -2f;
-
// How long a partial wake lock must be held until we consider it a long wake lock.
static final long MIN_LONG_WAKE_CHECK_INTERVAL = 60*1000;
@@ -619,7 +617,6 @@
public final float mScreenBrightnessMinimum;
public final float mScreenBrightnessMaximum;
public final float mScreenBrightnessDefault;
- public final float mScreenBrightnessDoze;
public final float mScreenBrightnessDim;
// Value we store for tracking face down behavior.
@@ -1219,8 +1216,6 @@
.config_screenBrightnessSettingMaximumFloat);
final float def = mContext.getResources().getFloat(com.android.internal.R.dimen
.config_screenBrightnessSettingDefaultFloat);
- final float doze = mContext.getResources().getFloat(com.android.internal.R.dimen
- .config_screenBrightnessDozeFloat);
final float dim = mContext.getResources().getFloat(com.android.internal.R.dimen
.config_screenBrightnessDimFloat);
@@ -1240,13 +1235,6 @@
mScreenBrightnessMaximum = max;
mScreenBrightnessDefault = def;
}
- if (doze == INVALID_BRIGHTNESS_IN_CONFIG) {
- mScreenBrightnessDoze = BrightnessSynchronizer.brightnessIntToFloat(
- mContext.getResources().getInteger(com.android.internal.R.integer
- .config_screenBrightnessDoze));
- } else {
- mScreenBrightnessDoze = doze;
- }
if (dim == INVALID_BRIGHTNESS_IN_CONFIG) {
mScreenBrightnessDim = BrightnessSynchronizer.brightnessIntToFloat(
mContext.getResources().getInteger(com.android.internal.R.integer
@@ -6090,8 +6078,6 @@
return mScreenBrightnessDefault;
case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM:
return mScreenBrightnessDim;
- case PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE:
- return mScreenBrightnessDoze;
default:
return PowerManager.BRIGHTNESS_INVALID_FLOAT;
}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index dfccd1a..bca81f52 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -1283,7 +1283,7 @@
}
case FrameworkStatsLog.PROXY_BYTES_TRANSFER_BY_FG_BG: {
final NetworkStats stats = getUidNetworkStatsSnapshotForTemplate(
- new NetworkTemplate.Builder(MATCH_PROXY).build(), /*includeTags=*/true);
+ new NetworkTemplate.Builder(MATCH_PROXY).build(), /*includeTags=*/false);
if (stats != null) {
ret.add(new NetworkStatsExt(sliceNetworkStatsByUidTagAndMetered(stats),
new int[]{TRANSPORT_BLUETOOTH},
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 8768074..3076b87 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -7173,7 +7173,7 @@
Slog.v(TAG, "Eval win " + w + ": isDrawn=" + w.isDrawn()
+ ", isAnimationSet=" + isAnimationSet);
if (!w.isDrawn()) {
- Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurfaceController
+ Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurfaceControl
+ " pv=" + w.isVisibleByPolicy()
+ " mDrawState=" + winAnimator.drawStateToString()
+ " ph=" + w.isParentWindowHidden() + " th=" + mVisibleRequested
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 59b5da8..ff46b33 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4709,6 +4709,10 @@
// Update stored global config and notify everyone about the change.
mRootWindowContainer.onConfigurationChanged(mTempConfig);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ if ((changes & ActivityInfo.CONFIG_ORIENTATION) != 0) {
+ FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_ORIENTATION_CHANGED,
+ values.orientation);
+ }
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return changes;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 3652c4d..9371149 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4082,12 +4082,12 @@
final Transaction t = mWmService.mTransactionFactory.get();
forAllWindows(w -> {
final WindowStateAnimator wsa = w.mWinAnimator;
- if (wsa.mSurfaceController == null) {
+ if (wsa.mSurfaceControl == null) {
return;
}
if (!mWmService.mSessions.contains(wsa.mSession)) {
Slog.w(TAG_WM, "LEAKED SURFACE (session doesn't exist): "
- + w + " surface=" + wsa.mSurfaceController
+ + w + " surface=" + wsa.mSurfaceControl
+ " token=" + w.mToken
+ " pid=" + w.mSession.mPid
+ " uid=" + w.mSession.mUid);
@@ -4096,7 +4096,7 @@
mTmpWindow = w;
} else if (w.mActivityRecord != null && !w.mActivityRecord.isClientVisible()) {
Slog.w(TAG_WM, "LEAKED SURFACE (app token hidden): "
- + w + " surface=" + wsa.mSurfaceController
+ + w + " surface=" + wsa.mSurfaceControl
+ " token=" + w.mActivityRecord);
ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE LEAK DESTROY: %s", w);
wsa.destroySurface(t);
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 74dbd15..b496a65 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -623,7 +623,7 @@
// occlusion detection depending on the type or if it's a trusted overlay.
populateOverlayInputInfo(inputWindowHandle, w);
setInputWindowInfoIfNeeded(mInputTransaction,
- w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);
+ w.mWinAnimator.mSurfaceControl, inputWindowHandle);
return;
}
// Skip this window because it cannot possibly receive input.
@@ -687,7 +687,7 @@
if (w.mWinAnimator.hasSurface()) {
populateInputWindowHandle(inputWindowHandle, w);
setInputWindowInfoIfNeeded(mInputTransaction,
- w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);
+ w.mWinAnimator.mSurfaceControl, inputWindowHandle);
}
}
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 99697de..f2ccbc4 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -667,7 +667,7 @@
boolean reclaimSomeSurfaceMemory(WindowStateAnimator winAnimator, String operation,
boolean secure) {
- final WindowSurfaceController surfaceController = winAnimator.mSurfaceController;
+ final SurfaceControl surfaceControl = winAnimator.mSurfaceControl;
boolean leakedSurface = false;
boolean killedApps = false;
EventLogTags.writeWmNoSurfaceMemory(winAnimator.mWin.toString(),
@@ -692,7 +692,7 @@
return;
}
final WindowStateAnimator wsa = w.mWinAnimator;
- if (wsa.mSurfaceController != null) {
+ if (wsa.mSurfaceControl != null) {
pidCandidates.append(wsa.mSession.mPid, wsa.mSession.mPid);
}
}, false /* traverseTopToBottom */);
@@ -717,7 +717,7 @@
// app to request another one.
Slog.w(TAG_WM,
"Looks like we have reclaimed some memory, clearing surface for retry.");
- if (surfaceController != null) {
+ if (surfaceControl != null) {
ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
"SURFACE RECOVER DESTROY: %s", winAnimator.mWin);
SurfaceControl.Transaction t = mWmService.mTransactionFactory.get();
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 31fda77..db0374e 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -324,7 +324,7 @@
if (!w.mToken.mRoundedCornerOverlay || !w.isVisible() || !w.mWinAnimator.hasSurface()) {
return;
}
- t.setSkipScreenshot(w.mWinAnimator.mSurfaceController.mSurfaceControl, skipScreenshot);
+ t.setSkipScreenshot(w.mWinAnimator.mSurfaceControl, skipScreenshot);
}, false);
if (!skipScreenshot) {
// Use sync apply to apply the change immediately, so that the next
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 75e3e65..f5108f5b 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -109,8 +109,8 @@
private final String mStringName;
SurfaceSession mSurfaceSession;
private final ArrayList<WindowState> mAddedWindows = new ArrayList<>();
- /** Set of visible alert/app-overlay window surfaces connected to this session. */
- private final ArraySet<WindowSurfaceController> mAlertWindowSurfaces = new ArraySet<>();
+ /** Set of visible alert/app-overlay windows connected to this session. */
+ private final ArraySet<WindowState> mAlertWindows = new ArraySet<>();
private final DragDropController mDragDropController;
final boolean mCanAddInternalSystemWindow;
boolean mCanForceShowingInsets;
@@ -769,9 +769,8 @@
return !mAddedWindows.isEmpty();
}
- void onWindowSurfaceVisibilityChanged(WindowSurfaceController surfaceController,
- boolean visible, int type) {
-
+ void onWindowSurfaceVisibilityChanged(WindowState window, boolean visible) {
+ final int type = window.mAttrs.type;
if (!isSystemAlertWindowType(type)) {
return;
}
@@ -782,7 +781,7 @@
final boolean noSystemOverlayPermission =
!mCanAddInternalSystemWindow && !mCanCreateSystemApplicationOverlay;
if (visible) {
- changed = mAlertWindowSurfaces.add(surfaceController);
+ changed = mAlertWindows.add(window);
if (type == TYPE_APPLICATION_OVERLAY) {
MetricsLoggerWrapper.logAppOverlayEnter(mUid, mPackageName, changed, type,
false /* set false to only log for TYPE_APPLICATION_OVERLAY */);
@@ -791,7 +790,7 @@
true /* only log for non-TYPE_APPLICATION_OVERLAY */);
}
} else {
- changed = mAlertWindowSurfaces.remove(surfaceController);
+ changed = mAlertWindows.remove(window);
if (type == TYPE_APPLICATION_OVERLAY) {
MetricsLoggerWrapper.logAppOverlayExit(mUid, mPackageName, changed, type,
false /* set false to only log for TYPE_APPLICATION_OVERLAY */);
@@ -802,7 +801,7 @@
}
if (changed && noSystemOverlayPermission) {
- if (mAlertWindowSurfaces.isEmpty()) {
+ if (mAlertWindows.isEmpty()) {
cancelAlertWindowNotification();
} else if (mAlertWindowNotification == null && !isSatellitePointingUiPackage()) {
mAlertWindowNotification = new AlertWindowNotification(mService, mPackageName);
@@ -815,7 +814,7 @@
if (changed && mPid != WindowManagerService.MY_PID) {
// Notify activity manager that the process contains overlay/alert windows, so it can
// adjust the importance score for the process.
- setHasOverlayUi(!mAlertWindowSurfaces.isEmpty());
+ setHasOverlayUi(!mAlertWindows.isEmpty());
}
}
@@ -859,7 +858,7 @@
}
mSurfaceSession = null;
mAddedWindows.clear();
- mAlertWindowSurfaces.clear();
+ mAlertWindows.clear();
setHasOverlayUi(false);
cancelAlertWindowNotification();
}
@@ -880,7 +879,7 @@
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("numWindow="); pw.print(mAddedWindows.size());
pw.print(" mCanAddInternalSystemWindow="); pw.print(mCanAddInternalSystemWindow);
- pw.print(" mAlertWindowSurfaces="); pw.print(mAlertWindowSurfaces);
+ pw.print(" mAlertWindows="); pw.print(mAlertWindows);
pw.print(" mClientDead="); pw.print(mClientDead);
pw.print(" mSurfaceSession="); pw.println(mSurfaceSession);
pw.print(prefix); pw.print("mPackageName="); pw.println(mPackageName);
@@ -896,9 +895,9 @@
/** @return {@code true} if there is an alert window surface on the given display. */
boolean hasAlertWindowSurfaces(DisplayContent displayContent) {
- for (int i = mAlertWindowSurfaces.size() - 1; i >= 0; i--) {
- final WindowSurfaceController surfaceController = mAlertWindowSurfaces.valueAt(i);
- if (surfaceController.mAnimator.mWin.getDisplayContent() == displayContent) {
+ for (int i = mAlertWindows.size() - 1; i >= 0; i--) {
+ final WindowState window = mAlertWindows.valueAt(i);
+ if (window.mDisplayContent == displayContent) {
return true;
}
}
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 439c7bb..561ff7d 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -1169,16 +1169,6 @@
}
}
- @Override
- public boolean isActivityEmbedded(IBinder activityToken) {
- synchronized (mGlobalLock) {
- final ActivityRecord activity = ActivityRecord.forTokenLocked(activityToken);
- return activity != null
- ? activity.isEmbeddedInHostContainer()
- : false;
- }
- }
-
@VisibleForTesting
@NonNull
IApplicationThread getAppThread(int pid, int uid) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 2a3e945..5336044 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2126,6 +2126,16 @@
}
/**
+
+ * Wallpaper will set itself as target if it wants to keep itself visible without a target.
+ */
+ private static boolean wallpaperIsOwnTarget(WallpaperWindowToken wallpaper) {
+ final WindowState target =
+ wallpaper.getDisplayContent().mWallpaperController.getWallpaperTarget();
+ return target != null && target.isDescendantOf(wallpaper);
+ }
+
+ /**
* Reset waitingToshow for all wallpapers, and commit the visibility of the visible ones
*/
private void commitVisibleWallpapers(SurfaceControl.Transaction t) {
@@ -2133,8 +2143,13 @@
for (int i = mParticipants.size() - 1; i >= 0; --i) {
final WallpaperWindowToken wallpaper = mParticipants.valueAt(i).asWallpaperToken();
if (wallpaper != null) {
- if (!wallpaper.isVisible() && wallpaper.isVisibleRequested()) {
+ if (!wallpaper.isVisible() && (wallpaper.isVisibleRequested()
+ || (Flags.ensureWallpaperInTransitions() && showWallpaper))) {
wallpaper.commitVisibility(showWallpaper);
+ } else if (Flags.ensureWallpaperInTransitions() && wallpaper.isVisible()
+ && !showWallpaper && !wallpaper.getDisplayContent().isKeyguardLocked()
+ && !wallpaperIsOwnTarget(wallpaper)) {
+ wallpaper.setVisibleRequested(false);
}
if (showWallpaper && Flags.ensureWallpaperInTransitions()
&& wallpaper.isVisibleRequested()
@@ -2556,11 +2571,10 @@
if (wc.asWindowState() != null) continue;
final ChangeInfo changeInfo = changes.get(wc);
- // Reject no-ops, unless wallpaper
- if (!changeInfo.hasChanged()
- && (!Flags.ensureWallpaperInTransitions() || wc.asWallpaperToken() == null)) {
+ // Reject no-ops
+ if (!changeInfo.hasChanged()) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- " Rejecting as no-op: %s", wc);
+ " Rejecting as no-op: %s vis: %b", wc, wc.isVisibleRequested());
continue;
}
targets.add(changeInfo);
diff --git a/services/core/java/com/android/server/wm/TransparentPolicy.java b/services/core/java/com/android/server/wm/TransparentPolicy.java
index 3044abd..cdb14ab 100644
--- a/services/core/java/com/android/server/wm/TransparentPolicy.java
+++ b/services/core/java/com/android/server/wm/TransparentPolicy.java
@@ -162,10 +162,6 @@
mTransparentPolicyState.clearInheritedCompatDisplayInsets();
}
- TransparentPolicyState getTransparentPolicyState() {
- return mTransparentPolicyState;
- }
-
/**
* In case of translucent activities, it consumes the {@link ActivityRecord} of the first opaque
* activity beneath using the given consumer and returns {@code true}.
@@ -176,7 +172,7 @@
@NonNull
Optional<ActivityRecord> getFirstOpaqueActivity() {
- return isRunning() ? Optional.of(mTransparentPolicyState.mFirstOpaqueActivity)
+ return isRunning() ? Optional.ofNullable(mTransparentPolicyState.mFirstOpaqueActivity)
: Optional.empty();
}
@@ -216,10 +212,6 @@
SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
}
- private void inheritConfiguration(ActivityRecord firstOpaque) {
- mTransparentPolicyState.inheritFromOpaque(firstOpaque);
- }
-
/**
* Encapsulate the state for the current translucent activity when the transparent policy
* has started.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 700c069..ebbf6e3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2570,7 +2570,7 @@
// surface, let the client use that, but don't create new surface at this
// point.
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: getSurface");
- winAnimator.mSurfaceController.getSurfaceControl(outSurfaceControl);
+ winAnimator.getSurfaceControl(outSurfaceControl);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
} else {
if (DEBUG_VISIBILITY) Slog.i(TAG_WM, "Releasing surface in: " + win);
@@ -2766,15 +2766,15 @@
result |= RELAYOUT_RES_SURFACE_CHANGED;
}
- WindowSurfaceController surfaceController;
+ SurfaceControl surfaceControl;
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createSurfaceControl");
- surfaceController = winAnimator.createSurfaceLocked();
+ surfaceControl = winAnimator.createSurfaceLocked();
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
- if (surfaceController != null) {
- surfaceController.getSurfaceControl(outSurfaceControl);
+ if (surfaceControl != null) {
+ winAnimator.getSurfaceControl(outSurfaceControl);
ProtoLog.i(WM_SHOW_TRANSACTIONS, "OUT SURFACE %s: copied", outSurfaceControl);
} else {
@@ -6773,11 +6773,11 @@
if (windowState == null) {
return false;
}
- WindowSurfaceController surfaceController = windowState.mWinAnimator.mSurfaceController;
- if (surfaceController == null) {
+ final SurfaceControl surfaceControl = windowState.mWinAnimator.mSurfaceControl;
+ if (surfaceControl == null) {
return false;
}
- return surfaceController.clearWindowContentFrameStats();
+ return surfaceControl.clearContentFrameStats();
}
}
@@ -6792,15 +6792,15 @@
if (windowState == null) {
return null;
}
- WindowSurfaceController surfaceController = windowState.mWinAnimator.mSurfaceController;
- if (surfaceController == null) {
+ final SurfaceControl surfaceControl = windowState.mWinAnimator.mSurfaceControl;
+ if (surfaceControl == null) {
return null;
}
if (mTempWindowRenderStats == null) {
mTempWindowRenderStats = new WindowContentFrameStats();
}
WindowContentFrameStats stats = mTempWindowRenderStats;
- if (!surfaceController.getWindowContentFrameStats(stats)) {
+ if (!surfaceControl.getContentFrameStats(stats)) {
return null;
}
return stats;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 390e34c..9ebb89d 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1501,7 +1501,7 @@
if (isDrawn()) {
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Orientation not waiting for draw in %s, surfaceController %s", this,
- winAnimator.mSurfaceController);
+ winAnimator.mSurfaceControl);
setOrientationChanging(false);
mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
- mWmService.mDisplayFreezeTime);
@@ -2425,7 +2425,7 @@
ProtoLog.v(WM_DEBUG_FOCUS, "Remove client=%x, surfaceController=%s Callers=%s",
System.identityHashCode(mClient.asBinder()),
- mWinAnimator.mSurfaceController,
+ mWinAnimator.mSurfaceControl,
Debug.getCallers(5));
final DisplayContent displayContent = getDisplayContent();
@@ -2436,10 +2436,10 @@
mOnBackInvokedCallbackInfo = null;
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "Remove %s: mSurfaceController=%s mAnimatingExit=%b mRemoveOnExit=%b "
+ "Remove %s: mSurfaceControl=%s mAnimatingExit=%b mRemoveOnExit=%b "
+ "mHasSurface=%b surfaceShowing=%b animating=%b app-animation=%b "
+ "mDisplayFrozen=%b callers=%s",
- this, mWinAnimator.mSurfaceController, mAnimatingExit, mRemoveOnExit,
+ this, mWinAnimator.mSurfaceControl, mAnimatingExit, mRemoveOnExit,
mHasSurface, mWinAnimator.getShown(),
isAnimating(TRANSITION | PARENTS),
mActivityRecord != null && mActivityRecord.isAnimating(PARENTS | TRANSITION),
@@ -2616,7 +2616,7 @@
+ isVisibleRequestedOrAdding() + " isVisible: " + (isVisible()
&& mActivityRecord != null && mActivityRecord.isVisible()));
if (!isVisibleRequestedOrAdding()) {
- Slog.i(TAG_WM, " mSurfaceController=" + mWinAnimator.mSurfaceController
+ Slog.i(TAG_WM, " mSurfaceControl=" + mWinAnimator.mSurfaceControl
+ " relayoutCalled=" + mRelayoutCalled
+ " viewVis=" + mViewVisibility
+ " policyVis=" + isVisibleByPolicy()
@@ -4457,7 +4457,7 @@
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowState c = mChildren.get(i);
- if (c.mWinAnimator.mSurfaceController != null) {
+ if (c.mWinAnimator.mSurfaceControl != null) {
c.performShowLocked();
// It hadn't been shown, which means layout not performed on it, so now we
// want to make sure to do a layout. If called from within the transaction
@@ -4914,7 +4914,7 @@
Slog.v(TAG, "Win " + this + ": isDrawn=" + isDrawn()
+ ", animating=" + isAnimating(TRANSITION | PARENTS));
if (!isDrawn()) {
- Slog.v(TAG, "Not displayed: s=" + mWinAnimator.mSurfaceController
+ Slog.v(TAG, "Not displayed: s=" + mWinAnimator.mSurfaceControl
+ " pv=" + isVisibleByPolicy()
+ " mDrawState=" + mWinAnimator.mDrawState
+ " ph=" + isParentWindowHidden()
@@ -5535,13 +5535,13 @@
// been defined and so we can use static layers and leave it that way.
if (w.mAttrs.type == TYPE_APPLICATION_MEDIA) {
if (mWinAnimator.hasSurface()) {
- w.assignRelativeLayer(t, mWinAnimator.mSurfaceController.mSurfaceControl, -2);
+ w.assignRelativeLayer(t, mWinAnimator.mSurfaceControl, -2);
} else {
w.assignLayer(t, -2);
}
} else if (w.mAttrs.type == TYPE_APPLICATION_MEDIA_OVERLAY) {
if (mWinAnimator.hasSurface()) {
- w.assignRelativeLayer(t, mWinAnimator.mSurfaceController.mSurfaceControl, -1);
+ w.assignRelativeLayer(t, mWinAnimator.mSurfaceControl, -1);
} else {
w.assignLayer(t, -1);
}
@@ -5717,7 +5717,7 @@
}
SurfaceControl getClientViewRootSurface() {
- return mWinAnimator.getSurfaceControl();
+ return mWinAnimator.mSurfaceControl;
}
/** Drops a buffer for this window's view-root from a transaction */
@@ -6117,11 +6117,10 @@
}
getPendingTransaction().setSecure(mSurfaceControl, isSecure);
} else {
- if (mWinAnimator.mSurfaceController == null
- || mWinAnimator.mSurfaceController.mSurfaceControl == null) {
+ if (mWinAnimator.mSurfaceControl == null) {
return;
}
- getPendingTransaction().setSecure(mWinAnimator.mSurfaceController.mSurfaceControl,
+ getPendingTransaction().setSecure(mWinAnimator.mSurfaceControl,
isSecure);
}
if (mDisplayContent != null) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 397a6357..24a2a62 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -16,6 +16,10 @@
package com.android.server.wm;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.SurfaceControl.METADATA_OWNER_PID;
+import static android.view.SurfaceControl.METADATA_OWNER_UID;
+import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -44,6 +48,7 @@
import static com.android.server.wm.WindowStateAnimatorProto.DRAW_STATE;
import static com.android.server.wm.WindowStateAnimatorProto.SURFACE;
import static com.android.server.wm.WindowStateAnimatorProto.SYSTEM_DECOR_RECT;
+import static com.android.server.wm.WindowSurfaceControllerProto.SHOWN;
import static com.android.window.flags.Flags.secureWindowState;
import static com.android.window.flags.Flags.setScPropertiesInClient;
@@ -52,6 +57,7 @@
import android.graphics.Rect;
import android.os.Debug;
import android.os.Trace;
+import android.util.EventLog;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.Surface.OutOfResourcesException;
@@ -95,12 +101,13 @@
final Session mSession;
final WindowManagerPolicy mPolicy;
final Context mContext;
- final boolean mIsWallpaper;
private final WallpaperController mWallpaperControllerLocked;
boolean mAnimationIsEntrance;
- WindowSurfaceController mSurfaceController;
+ SurfaceControl mSurfaceControl;
+ private boolean mSurfaceShown;
+ private String mTitle;
float mShownAlpha = 0;
float mAlpha = 0;
@@ -164,7 +171,6 @@
mWin = win;
mSession = win.mSession;
mAttrType = win.mAttrs.type;
- mIsWallpaper = win.mIsWallpaper;
mWallpaperControllerLocked = win.getDisplayContent().mWallpaperController;
}
@@ -198,14 +204,30 @@
}
void hide(SurfaceControl.Transaction transaction, String reason) {
- if (!mLastHidden) {
- //dump();
- mLastHidden = true;
-
- if (mSurfaceController != null) {
- mSurfaceController.hide(transaction, reason);
- }
+ if (mLastHidden) {
+ return;
}
+ mLastHidden = true;
+ if (mSurfaceControl == null || !mSurfaceShown) {
+ return;
+ }
+ ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE HIDE ( %s ): %s", reason, mTitle);
+
+ setShown(false);
+ transaction.hide(mSurfaceControl);
+ if (mWin.mIsWallpaper) {
+ final DisplayContent dc = mWin.getDisplayContent();
+ EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE,
+ dc.mDisplayId, 0 /* request hidden */,
+ String.valueOf(dc.mWallpaperController.getWallpaperTarget()));
+ }
+ }
+
+ private void setShown(boolean surfaceShown) {
+ mSurfaceShown = surfaceShown;
+ mService.updateNonSystemOverlayWindowsVisibilityIfNeeded(mWin, surfaceShown);
+ mWin.onSurfaceShownChanged(surfaceShown);
+ mSession.onWindowSurfaceVisibilityChanged(mWin, mSurfaceShown);
}
boolean finishDrawingLocked(SurfaceControl.Transaction postDrawTransaction) {
@@ -221,7 +243,7 @@
if (mDrawState == DRAW_PENDING) {
ProtoLog.v(WM_DEBUG_DRAW,
"finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING %s in %s", mWin,
- mSurfaceController);
+ mSurfaceControl);
if (startingWindow) {
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Draw state now committed in %s", mWin);
}
@@ -248,7 +270,7 @@
return false;
}
ProtoLog.i(WM_DEBUG_ANIM, "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW %s",
- mSurfaceController);
+ mSurfaceControl);
mDrawState = READY_TO_SHOW;
boolean result = false;
final ActivityRecord activity = mWin.mActivityRecord;
@@ -271,11 +293,11 @@
}
}
- WindowSurfaceController createSurfaceLocked() {
+ SurfaceControl createSurfaceLocked() {
final WindowState w = mWin;
- if (mSurfaceController != null) {
- return mSurfaceController;
+ if (mSurfaceControl != null) {
+ return mSurfaceControl;
}
w.setHasSurface(false);
@@ -312,10 +334,22 @@
final boolean isHwAccelerated = (attrs.flags & FLAG_HARDWARE_ACCELERATED) != 0;
final int format = isHwAccelerated ? PixelFormat.TRANSLUCENT : attrs.format;
- mSurfaceController = new WindowSurfaceController(attrs.getTitle().toString(), format,
- flags, this, attrs.type);
+ mTitle = attrs.getTitle().toString();
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
+ mSurfaceControl = mWin.makeSurface()
+ .setParent(mWin.mSurfaceControl)
+ .setName(mTitle)
+ .setFormat(format)
+ .setFlags(flags)
+ .setMetadata(METADATA_WINDOW_TYPE, attrs.type)
+ .setMetadata(METADATA_OWNER_UID, mSession.mUid)
+ .setMetadata(METADATA_OWNER_PID, mSession.mPid)
+ .setCallsite("WindowSurfaceController")
+ .setBLASTLayer().build();
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+
if (!setScPropertiesInClient()) {
- mSurfaceController.setColorSpaceAgnostic(w.getPendingTransaction(),
+ setColorSpaceAgnosticLocked(
(attrs.privateFlags & LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC) != 0);
}
@@ -326,7 +360,7 @@
ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
" CREATE SURFACE %s IN SESSION %s: pid=%d format=%d flags=0x%x / %s",
- mSurfaceController, mSession.mSurfaceSession, mSession.mPid, attrs.format,
+ mSurfaceControl, mSession.mSurfaceSession, mSession.mPid, attrs.format,
flags, this);
} catch (OutOfResourcesException e) {
Slog.w(TAG, "OutOfResourcesException creating surface");
@@ -340,7 +374,7 @@
}
if (DEBUG) {
- Slog.v(TAG, "Got surface: " + mSurfaceController
+ Slog.v(TAG, "Got surface: " + mSurfaceControl
+ ", set left=" + w.getFrame().left + " top=" + w.getFrame().top);
}
@@ -353,15 +387,19 @@
mLastHidden = true;
if (DEBUG) Slog.v(TAG, "Created surface " + this);
- return mSurfaceController;
+ return mSurfaceControl;
}
boolean hasSurface() {
- return mSurfaceController != null && mSurfaceController.hasSurface();
+ return mSurfaceControl != null;
+ }
+
+ void getSurfaceControl(SurfaceControl outSurfaceControl) {
+ outSurfaceControl.copyFrom(mSurfaceControl, "WindowStateAnimator.getSurfaceControl");
}
void destroySurfaceLocked(SurfaceControl.Transaction t) {
- if (mSurfaceController == null) {
+ if (mSurfaceControl == null) {
return;
}
@@ -370,7 +408,7 @@
try {
if (DEBUG_VISIBILITY) {
logWithStack(TAG, "Window " + this + " destroying surface "
- + mSurfaceController + ", session " + mSession);
+ + mSurfaceControl + ", session " + mSession);
}
ProtoLog.i(WM_SHOW_SURFACE_ALLOC, "SURFACE DESTROY: %s. %s",
mWin, new RuntimeException().fillInStackTrace());
@@ -384,23 +422,13 @@
}
} catch (RuntimeException e) {
Slog.w(TAG, "Exception thrown when destroying Window " + this
- + " surface " + mSurfaceController + " session " + mSession + ": "
+ + " surface " + mSurfaceControl + " session " + mSession + ": "
+ e.toString());
}
-
- // Whether the surface was preserved (and copied to mPendingDestroySurface) or not, it
- // needs to be cleared to match the WindowState.mHasSurface state. It is also necessary
- // so it can be recreated successfully in mPendingDestroySurface case.
- mWin.setHasSurface(false);
- if (mSurfaceController != null) {
- mSurfaceController.setShown(false);
- }
- mSurfaceController = null;
- mDrawState = NO_SURFACE;
}
void computeShownFrameLocked() {
- if (mIsWallpaper && mService.mRoot.mWallpaperActionPending) {
+ if (mWin.mIsWallpaper && mService.mRoot.mWallpaperActionPending) {
return;
} else if (mWin.isDragResizeChanged()) {
// This window is awaiting a relayout because user just started (or ended)
@@ -454,14 +482,13 @@
mLastAlpha = mShownAlpha;
ProtoLog.i(WM_SHOW_TRANSACTIONS,
"SURFACE controller=%s alpha=%f HScale=%f, VScale=%f: %s",
- mSurfaceController, mShownAlpha, w.mHScale, w.mVScale, w);
+ mSurfaceControl, mShownAlpha, w.mHScale, w.mVScale, w);
- boolean prepared =
- mSurfaceController.prepareToShowInTransaction(t, mShownAlpha);
+ t.setAlpha(mSurfaceControl, mShownAlpha);
- if (prepared && mDrawState == HAS_DRAWN) {
+ if (mDrawState == HAS_DRAWN) {
if (mLastHidden) {
- mSurfaceController.showRobustly(t);
+ showRobustly(t);
mLastHidden = false;
final DisplayContent displayContent = w.getDisplayContent();
if (!displayContent.getLastHasContent()) {
@@ -494,18 +521,38 @@
}
}
- void setOpaqueLocked(boolean isOpaque) {
- if (mSurfaceController == null) {
+ private void showRobustly(SurfaceControl.Transaction t) {
+ if (mSurfaceShown) {
return;
}
- mSurfaceController.setOpaque(isOpaque);
+
+ ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE SHOW (performLayout): %s", mTitle);
+ if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + this + " during relayout");
+ setShown(true);
+ t.show(mSurfaceControl);
+ if (mWin.mIsWallpaper) {
+ final DisplayContent dc = mWin.mDisplayContent;
+ EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE,
+ dc.mDisplayId, 1 /* request shown */,
+ String.valueOf(dc.mWallpaperController.getWallpaperTarget()));
+ }
+ }
+
+ void setOpaqueLocked(boolean isOpaque) {
+ if (mSurfaceControl == null) {
+ return;
+ }
+ ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isOpaque=%b: %s", isOpaque, mTitle);
+ mWin.getPendingTransaction().setOpaque(mSurfaceControl, isOpaque);
+ mService.scheduleAnimationLocked();
}
void setColorSpaceAgnosticLocked(boolean agnostic) {
- if (mSurfaceController == null) {
+ if (mSurfaceControl == null) {
return;
}
- mSurfaceController.setColorSpaceAgnostic(mWin.getPendingTransaction(), agnostic);
+ ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isColorSpaceAgnostic=%b: %s", agnostic, mTitle);
+ mWin.getPendingTransaction().setColorSpaceAgnostic(mSurfaceControl, agnostic);
}
void applyEnterAnimationLocked() {
@@ -521,7 +568,7 @@
// should be controlled by ActivityRecord in general. Wallpaper is also excluded because
// WallpaperController should handle it. Also skip play enter animation for the window
// below starting window.
- if (mAttrType != TYPE_BASE_APPLICATION && !mIsWallpaper
+ if (mAttrType != TYPE_BASE_APPLICATION && !mWin.mIsWallpaper
&& !(mWin.mActivityRecord != null && mWin.mActivityRecord.hasStartingWindow())) {
applyAnimationLocked(transit, true);
}
@@ -614,8 +661,10 @@
void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
- if (mSurfaceController != null) {
- mSurfaceController.dumpDebug(proto, SURFACE);
+ if (mSurfaceControl != null) {
+ final long dumpToken = proto.start(SURFACE);
+ proto.write(SHOWN, mSurfaceShown);
+ proto.end(dumpToken);
}
proto.write(DRAW_STATE, mDrawState);
mSystemDecorRect.dumpDebug(proto, SYSTEM_DECOR_RECT);
@@ -626,8 +675,11 @@
if (mAnimationIsEntrance) {
pw.print(prefix); pw.print(" mAnimationIsEntrance="); pw.print(mAnimationIsEntrance);
}
- if (mSurfaceController != null) {
- mSurfaceController.dump(pw, prefix, dumpAll);
+ if (mSurfaceControl != null) {
+ if (dumpAll) {
+ pw.print(prefix); pw.print("mSurface="); pw.println(mSurfaceControl);
+ }
+ pw.print(prefix); pw.print("Surface: shown="); pw.print(mSurfaceShown);
}
if (dumpAll) {
pw.print(prefix); pw.print("mDrawState="); pw.print(drawStateToString());
@@ -659,31 +711,24 @@
}
boolean getShown() {
- if (mSurfaceController != null) {
- return mSurfaceController.getShown();
- }
- return false;
+ return mSurfaceControl != null && mSurfaceShown;
}
void destroySurface(SurfaceControl.Transaction t) {
- try {
- if (mSurfaceController != null) {
- mSurfaceController.destroy(t);
- }
- } catch (RuntimeException e) {
- Slog.w(TAG, "Exception thrown when destroying surface " + this
- + " surface " + mSurfaceController + " session " + mSession + ": " + e);
- } finally {
- mWin.setHasSurface(false);
- mSurfaceController = null;
- mDrawState = NO_SURFACE;
+ if (mSurfaceControl == null) {
+ return;
}
- }
-
- SurfaceControl getSurfaceControl() {
- if (!hasSurface()) {
- return null;
+ ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
+ "Destroying surface %s called by %s", this, Debug.getCallers(8));
+ if (mWin.mIsWallpaper && !mWin.mWindowRemovalAllowed && !mWin.mRemoveOnExit) {
+ // The wallpaper surface should have the same lifetime as its window.
+ Slog.e(TAG, "Unexpected removing wallpaper surface of " + mWin
+ + " by " + Debug.getCallers(8));
}
- return mSurfaceController.mSurfaceControl;
+ t.remove(mSurfaceControl);
+ setShown(false);
+ mSurfaceControl = null;
+ mWin.setHasSurface(false);
+ mDrawState = NO_SURFACE;
}
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
deleted file mode 100644
index d9766e0..0000000
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.SurfaceControl.METADATA_OWNER_PID;
-import static android.view.SurfaceControl.METADATA_OWNER_UID;
-import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
-
-import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
-import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowSurfaceControllerProto.SHOWN;
-
-import android.os.Debug;
-import android.os.Trace;
-import android.util.EventLog;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-import android.view.SurfaceControl;
-import android.view.WindowContentFrameStats;
-
-import com.android.internal.protolog.ProtoLog;
-
-import java.io.PrintWriter;
-
-class WindowSurfaceController {
- static final String TAG = TAG_WITH_CLASS_NAME ? "WindowSurfaceController" : TAG_WM;
-
- final WindowStateAnimator mAnimator;
-
- SurfaceControl mSurfaceControl;
-
- // Should only be set from within setShown().
- private boolean mSurfaceShown = false;
-
- private final String title;
-
- private final WindowManagerService mService;
-
- private final int mWindowType;
- private final Session mWindowSession;
-
-
- WindowSurfaceController(String name, int format, int flags, WindowStateAnimator animator,
- int windowType) {
- mAnimator = animator;
-
- title = name;
-
- mService = animator.mService;
- final WindowState win = animator.mWin;
- mWindowType = windowType;
- mWindowSession = win.mSession;
-
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
- mSurfaceControl = win.makeSurface()
- .setParent(win.getSurfaceControl())
- .setName(name)
- .setFormat(format)
- .setFlags(flags)
- .setMetadata(METADATA_WINDOW_TYPE, windowType)
- .setMetadata(METADATA_OWNER_UID, mWindowSession.mUid)
- .setMetadata(METADATA_OWNER_PID, mWindowSession.mPid)
- .setCallsite("WindowSurfaceController")
- .setBLASTLayer().build();
-
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
-
- void hide(SurfaceControl.Transaction transaction, String reason) {
- ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE HIDE ( %s ): %s", reason, title);
-
- if (mSurfaceShown) {
- hideSurface(transaction);
- }
- }
-
- private void hideSurface(SurfaceControl.Transaction transaction) {
- if (mSurfaceControl == null) {
- return;
- }
- setShown(false);
- try {
- transaction.hide(mSurfaceControl);
- if (mAnimator.mIsWallpaper) {
- final DisplayContent dc = mAnimator.mWin.getDisplayContent();
- EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE,
- dc.mDisplayId, 0 /* request hidden */,
- String.valueOf(dc.mWallpaperController.getWallpaperTarget()));
- }
- } catch (RuntimeException e) {
- Slog.w(TAG, "Exception hiding surface in " + this);
- }
- }
-
- void destroy(SurfaceControl.Transaction t) {
- ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
- "Destroying surface %s called by %s", this, Debug.getCallers(8));
- try {
- if (mSurfaceControl != null) {
- if (mAnimator.mIsWallpaper && !mAnimator.mWin.mWindowRemovalAllowed
- && !mAnimator.mWin.mRemoveOnExit) {
- // The wallpaper surface should have the same lifetime as its window.
- Slog.e(TAG, "Unexpected removing wallpaper surface of " + mAnimator.mWin
- + " by " + Debug.getCallers(8));
- }
- t.remove(mSurfaceControl);
- }
- } catch (RuntimeException e) {
- Slog.w(TAG, "Error destroying surface in: " + this, e);
- } finally {
- setShown(false);
- mSurfaceControl = null;
- }
- }
-
- boolean prepareToShowInTransaction(SurfaceControl.Transaction t, float alpha) {
- if (mSurfaceControl == null) {
- return false;
- }
-
- t.setAlpha(mSurfaceControl, alpha);
- return true;
- }
-
- void setOpaque(boolean isOpaque) {
- ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isOpaque=%b: %s", isOpaque, title);
-
- if (mSurfaceControl == null) {
- return;
- }
-
- mAnimator.mWin.getPendingTransaction().setOpaque(mSurfaceControl, isOpaque);
- mService.scheduleAnimationLocked();
- }
-
- void setColorSpaceAgnostic(SurfaceControl.Transaction t, boolean agnostic) {
- ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isColorSpaceAgnostic=%b: %s", agnostic, title);
-
- if (mSurfaceControl == null) {
- return;
- }
- t.setColorSpaceAgnostic(mSurfaceControl, agnostic);
- }
-
- void showRobustly(SurfaceControl.Transaction t) {
- ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE SHOW (performLayout): %s", title);
- if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + this
- + " during relayout");
-
- if (mSurfaceShown) {
- return;
- }
-
- setShown(true);
- t.show(mSurfaceControl);
- if (mAnimator.mIsWallpaper) {
- final DisplayContent dc = mAnimator.mWin.getDisplayContent();
- EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE,
- dc.mDisplayId, 1 /* request shown */,
- String.valueOf(dc.mWallpaperController.getWallpaperTarget()));
- }
- }
-
- boolean clearWindowContentFrameStats() {
- if (mSurfaceControl == null) {
- return false;
- }
- return mSurfaceControl.clearContentFrameStats();
- }
-
- boolean getWindowContentFrameStats(WindowContentFrameStats outStats) {
- if (mSurfaceControl == null) {
- return false;
- }
- return mSurfaceControl.getContentFrameStats(outStats);
- }
-
- boolean hasSurface() {
- return mSurfaceControl != null;
- }
-
- void getSurfaceControl(SurfaceControl outSurfaceControl) {
- outSurfaceControl.copyFrom(mSurfaceControl, "WindowSurfaceController.getSurfaceControl");
- }
-
- boolean getShown() {
- return mSurfaceShown;
- }
-
- void setShown(boolean surfaceShown) {
- mSurfaceShown = surfaceShown;
-
- mService.updateNonSystemOverlayWindowsVisibilityIfNeeded(mAnimator.mWin, surfaceShown);
-
- mAnimator.mWin.onSurfaceShownChanged(surfaceShown);
-
- if (mWindowSession != null) {
- mWindowSession.onWindowSurfaceVisibilityChanged(this, mSurfaceShown, mWindowType);
- }
- }
-
- void dumpDebug(ProtoOutputStream proto, long fieldId) {
- final long token = proto.start(fieldId);
- proto.write(SHOWN, mSurfaceShown);
- proto.end(token);
- }
-
- public void dump(PrintWriter pw, String prefix, boolean dumpAll) {
- if (dumpAll) {
- pw.print(prefix); pw.print("mSurface="); pw.println(mSurfaceControl);
- }
- pw.print(prefix); pw.print("Surface: shown="); pw.print(mSurfaceShown);
- }
-
- @Override
- public String toString() {
- return mSurfaceControl.toString();
- }
-}
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index ec7406a..4231149 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -179,6 +179,19 @@
<xs:element name="supportsVrr" type="xs:boolean" minOccurs="0">
<xs:annotation name="final"/>
</xs:element>
+ <!-- Table that translates doze brightness sensor values to brightness values in
+ the float scale [0, 1]; -1 means the current brightness should be kept.
+ The following formula should be used for conversion between nits and the float
+ scale: float = (nits - minNits) / (maxNits - minNits). minNits and maxNits are
+ defined in screenBrightnessMap. -->
+ <xs:element type="float-array" name="dozeBrightnessSensorValueToBrightness">
+ <xs:annotation name="final"/>
+ </xs:element>
+ <!-- The default screen brightness in the scale [0, 1] to use while the device is
+ dozing. -->
+ <xs:element type="nonNegativeDecimal" name="defaultDozeBrightness">
+ <xs:annotation name="final"/>
+ </xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
@@ -859,6 +872,12 @@
</xs:sequence>
</xs:complexType>
+ <xs:complexType name="float-array">
+ <xs:sequence>
+ <xs:element name="item" type="nonNegativeDecimal" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ </xs:complexType>
+
<xs:complexType name="usiVersion">
<xs:element name="majorVersion" type="xs:nonNegativeInteger"
minOccurs="1" maxOccurs="1">
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 68d74cf..cec2787 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -125,9 +125,11 @@
method public final java.math.BigInteger getAmbientLightHorizonLong();
method public final java.math.BigInteger getAmbientLightHorizonShort();
method public com.android.server.display.config.AutoBrightness getAutoBrightness();
+ method public final java.math.BigDecimal getDefaultDozeBrightness();
method @Nullable public final com.android.server.display.config.DensityMapping getDensityMapping();
method @NonNull public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholds();
method public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholdsIdle();
+ method public final com.android.server.display.config.FloatArray getDozeBrightnessSensorValueToBrightness();
method public final com.android.server.display.config.EvenDimmerMode getEvenDimmer();
method @Nullable public final com.android.server.display.config.HdrBrightnessConfig getHdrBrightnessConfig();
method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode();
@@ -163,9 +165,11 @@
method public final void setAmbientLightHorizonLong(java.math.BigInteger);
method public final void setAmbientLightHorizonShort(java.math.BigInteger);
method public void setAutoBrightness(com.android.server.display.config.AutoBrightness);
+ method public final void setDefaultDozeBrightness(java.math.BigDecimal);
method public final void setDensityMapping(@Nullable com.android.server.display.config.DensityMapping);
method public final void setDisplayBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);
method public final void setDisplayBrightnessChangeThresholdsIdle(com.android.server.display.config.Thresholds);
+ method public final void setDozeBrightnessSensorValueToBrightness(com.android.server.display.config.FloatArray);
method public final void setEvenDimmer(com.android.server.display.config.EvenDimmerMode);
method public final void setHdrBrightnessConfig(@Nullable com.android.server.display.config.HdrBrightnessConfig);
method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode);
@@ -214,6 +218,11 @@
method public void setTransitionPoint(java.math.BigDecimal);
}
+ public class FloatArray {
+ ctor public FloatArray();
+ method public java.util.List<java.math.BigDecimal> getItem();
+ }
+
public class HbmTiming {
ctor public HbmTiming();
method @NonNull public final java.math.BigInteger getTimeMaxSecs_all();
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index bacde10..c2a069d 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -68,7 +68,6 @@
import com.android.internal.view.IInputMethodManager;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
-import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
import com.android.server.input.InputManagerInternal;
import com.android.server.pm.UserManagerInternal;
@@ -161,7 +160,7 @@
.strictness(Strictness.LENIENT)
.spyStatic(InputMethodUtils.class)
.mockStatic(ServiceManager.class)
- .mockStatic(SystemServerInitThreadPool.class)
+ .spyStatic(AdditionalSubtypeMapRepository.class)
.startMocking();
mContext = spy(InstrumentationRegistry.getInstrumentation().getContext());
@@ -234,9 +233,8 @@
doNothing().when(() -> InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
any(PackageManager.class), anyList()));
- // Used by lazy initializing draw IMS nav bar at InputMethodManagerService#systemRunning(),
- // which is ok to be mocked out for now.
- doReturn(null).when(() -> SystemServerInitThreadPool.submit(any(), anyString()));
+ // The background writer thread in AdditionalSubtypeMapRepository should be stubbed out.
+ doNothing().when(AdditionalSubtypeMapRepository::startWriterThread);
mServiceThread =
new ServiceThread(
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 3437923..d450683 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -965,6 +965,51 @@
assertThat(supportedModeData.vsyncRate).isEqualTo(240);
}
+ @Test
+ public void testDozeBrightness_Ddc() throws IOException {
+ when(mFlags.isDozeBrightnessFloatEnabled()).thenReturn(true);
+ setupDisplayDeviceConfigFromDisplayConfigFile();
+
+ assertArrayEquals(new float[]{ -1, 0.1f, 0.2f, 0.3f, 0.4f },
+ mDisplayDeviceConfig.getDozeBrightnessSensorValueToBrightness(), SMALL_DELTA);
+ assertEquals(0.25f, mDisplayDeviceConfig.getDefaultDozeBrightness(), SMALL_DELTA);
+ }
+
+ @Test
+ public void testDefaultDozeBrightness_FallBackToConfigXmlFloat() throws IOException {
+ setupDisplayDeviceConfigFromConfigResourceFile();
+ when(mFlags.isDozeBrightnessFloatEnabled()).thenReturn(true);
+ when(mResources.getFloat(com.android.internal.R.dimen.config_screenBrightnessDozeFloat))
+ .thenReturn(0.31f);
+ when(mResources.getInteger(com.android.internal.R.integer.config_screenBrightnessDoze))
+ .thenReturn(90);
+
+ // Empty display config file
+ setupDisplayDeviceConfigFromDisplayConfigFile(
+ "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<displayConfiguration />\n");
+
+ assertEquals(0.31f, mDisplayDeviceConfig.getDefaultDozeBrightness(), ZERO_DELTA);
+ }
+
+ @Test
+ public void testDefaultDozeBrightness_FallBackToConfigXmlInt() throws IOException {
+ setupDisplayDeviceConfigFromConfigResourceFile();
+ when(mFlags.isDozeBrightnessFloatEnabled()).thenReturn(true);
+ when(mResources.getFloat(com.android.internal.R.dimen.config_screenBrightnessDozeFloat))
+ .thenReturn(DisplayDeviceConfig.INVALID_BRIGHTNESS_IN_CONFIG);
+ when(mResources.getInteger(com.android.internal.R.integer.config_screenBrightnessDoze))
+ .thenReturn(90);
+
+ // Empty display config file
+ setupDisplayDeviceConfigFromDisplayConfigFile(
+ "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<displayConfiguration />\n");
+
+ assertEquals(brightnessIntToFloat(90),
+ mDisplayDeviceConfig.getDefaultDozeBrightness(), ZERO_DELTA);
+ }
+
private String getValidLuxThrottling() {
return "<luxThrottling>\n"
+ " <brightnessLimitMap>\n"
@@ -1708,6 +1753,16 @@
+ "</point>"
+ "</luxThresholds>"
+ "</idleScreenRefreshRateTimeout>"
+ + "<dozeBrightnessSensorValueToBrightness>\n"
+ + "<item>-1</item>\n"
+ + "<item>0.1</item>\n"
+ + "<item>0.2</item>\n"
+ + "<item>0.3</item>\n"
+ + "<item>0.4</item>\n"
+ + "</dozeBrightnessSensorValueToBrightness>\n"
+ + "<defaultDozeBrightness>"
+ + "0.25"
+ + "</defaultDozeBrightness>\n"
+ "</displayConfiguration>\n";
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 5c29156..624c897 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -117,6 +117,7 @@
private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
private static final float PROX_SENSOR_MAX_RANGE = 5;
private static final float DOZE_SCALE_FACTOR = 0.34f;
+ private static final float DEFAULT_DOZE_BRIGHTNESS = 0.121f;
private static final float BRIGHTNESS_RAMP_RATE_MINIMUM = 0.0f;
private static final float BRIGHTNESS_RAMP_RATE_FAST_DECREASE = 0.3f;
@@ -2051,9 +2052,6 @@
@Test
public void testDefaultDozeBrightness() {
- float brightness = 0.121f;
- when(mPowerManagerMock.getBrightnessConstraint(
- PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE)).thenReturn(brightness);
mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, false);
mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
@@ -2069,15 +2067,25 @@
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- verify(mHolder.animator).animateTo(eq(brightness), /* linearSecondTarget= */ anyFloat(),
- eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+ verify(mHolder.animator).animateTo(eq(DEFAULT_DOZE_BRIGHTNESS),
+ /* linearSecondTarget= */ anyFloat(), eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE),
+ eq(false));
+
+ // The display device changes and the default doze brightness changes
+ setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+ mHolder.config, /* isEnabled= */ true);
+ when(mHolder.config.getDefaultDozeBrightness()).thenReturn(DEFAULT_DOZE_BRIGHTNESS / 2);
+ mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(DEFAULT_DOZE_BRIGHTNESS / 2),
+ /* linearSecondTarget= */ anyFloat(), eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE),
+ eq(false));
}
@Test
public void testDefaultDozeBrightness_ShouldNotBeUsedIfAutoBrightnessAllowedInDoze() {
- float brightness = 0.121f;
- when(mPowerManagerMock.getBrightnessConstraint(
- PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE)).thenReturn(brightness);
mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
@@ -2093,7 +2101,7 @@
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- verify(mHolder.animator, never()).animateTo(eq(brightness),
+ verify(mHolder.animator, never()).animateTo(eq(DEFAULT_DOZE_BRIGHTNESS),
/* linearSecondTarget= */ anyFloat(), /* rate= */ anyFloat(),
/* ignoreAnimationLimits= */ anyBoolean());
}
@@ -2151,6 +2159,8 @@
new SensorData(Sensor.STRING_TYPE_LIGHT, null));
when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux())
.thenReturn(new int[0]);
+ when(displayDeviceConfigMock.getDefaultDozeBrightness())
+ .thenReturn(DEFAULT_DOZE_BRIGHTNESS);
when(displayDeviceConfigMock.getBrightnessRampFastDecrease())
.thenReturn(BRIGHTNESS_RAMP_RATE_FAST_DECREASE);
diff --git a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
index 758c84a..ef9580c 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
@@ -101,7 +101,7 @@
mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer,
mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy,
mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class),
- mock(AudioServerPermissionProvider.class)) {
+ mock(AudioServerPermissionProvider.class), r -> r.run()) {
@Override
public int getDeviceForStream(int stream) {
return AudioSystem.DEVICE_OUT_SPEAKER;
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
index 2cb02bd..4645156 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
@@ -78,7 +78,7 @@
mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer,
mSettingsAdapter, mAudioVolumeGroupHelper, mAudioPolicyMock,
mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class),
- mock(AudioServerPermissionProvider.class)) {
+ mock(AudioServerPermissionProvider.class), r -> r.run()) {
@Override
public int getDeviceForStream(int stream) {
return AudioSystem.DEVICE_OUT_SPEAKER;
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
index 037c3c0..b7100ea 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
@@ -87,7 +87,7 @@
.thenReturn(AppOpsManager.MODE_ALLOWED);
mAudioService = new AudioService(mContext, mSpyAudioSystem, mSpySystemServer,
mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy, null,
- mMockAppOpsManager, mMockPermissionEnforcer, mMockPermissionProvider);
+ mMockAppOpsManager, mMockPermissionEnforcer, mMockPermissionProvider, r -> r.run());
}
/**
diff --git a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
index 27b552f..746645a 100644
--- a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
@@ -78,7 +78,7 @@
mAudioService = new AudioService(mContext, mAudioSystem, mSystemServer,
mSettingsAdapter, mAudioVolumeGroupHelper, mAudioPolicyMock,
mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class),
- mock(AudioServerPermissionProvider.class));
+ mock(AudioServerPermissionProvider.class), r -> r.run());
mTestLooper.dispatchAll();
}
diff --git a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
index 8e34ee1..e45ab31 100644
--- a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
@@ -160,7 +160,7 @@
@NonNull PermissionEnforcer enforcer,
AudioServerPermissionProvider permissionProvider) {
super(context, audioSystem, systemServer, settings, audioVolumeGroupHelper,
- audioPolicy, looper, appOps, enforcer, permissionProvider);
+ audioPolicy, looper, appOps, enforcer, permissionProvider, r -> r.run());
}
public void setDeviceForStream(int stream, int device) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index c1be5ca..63e3e5c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -714,7 +714,7 @@
// Simulate when the window is exiting and cleanupAnimation invoked
// (e.g. screen off during RecentsAnimation animating), will expect the window receives
// onExitAnimationDone to destroy the surface when the removal is allowed.
- win1.mWinAnimator.mSurfaceController = mock(WindowSurfaceController.class);
+ win1.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class);
win1.mHasSurface = true;
win1.mAnimatingExit = true;
win1.mRemoveOnExit = true;
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 11d9629..0bf27d1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -43,8 +43,6 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -758,12 +756,9 @@
// Simulating win1 has shown IME and being IME layering/input target
mDisplayContent.setImeLayeringTarget(win1);
mDisplayContent.setImeInputTarget(win1);
- mImeWindow.mWinAnimator.mSurfaceController = mock(WindowSurfaceController.class);
mImeWindow.mWinAnimator.hide(mDisplayContent.getPendingTransaction(), "test");
spyOn(mDisplayContent);
- doReturn(true).when(mImeWindow.mWinAnimator.mSurfaceController).hasSurface();
- doReturn(true).when(mImeWindow.mWinAnimator.mSurfaceController)
- .prepareToShowInTransaction(any(), anyFloat());
+ mImeWindow.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class);
makeWindowVisibleAndDrawn(mImeWindow);
assertTrue(mImeWindow.isOnScreen());
assertFalse(mImeWindow.isParentWindowHidden());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 9b48cb9..c65b76e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -31,7 +31,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.window.flags.Flags.multiCrop;
import static com.google.common.truth.Truth.assertThat;
@@ -551,10 +550,9 @@
}
private static void makeWallpaperWindowShown(WindowState w) {
- final WindowSurfaceController windowSurfaceController = mock(WindowSurfaceController.class);
- w.mWinAnimator.mSurfaceController = windowSurfaceController;
w.mWinAnimator.mLastAlpha = 1;
- when(windowSurfaceController.getShown()).thenReturn(true);
+ spyOn(w.mWinAnimator);
+ doReturn(true).when(w.mWinAnimator).getShown();
}
private WindowState createWallpaperWindow(DisplayContent dc, int width, int height) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 4958b90..89abe2f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -253,22 +253,18 @@
final Session session = createTestSession(mAtm, wpc.getPid(), wpc.mUid);
spyOn(session);
assertTrue(session.mCanAddInternalSystemWindow);
- final WindowSurfaceController winSurface = mock(WindowSurfaceController.class);
- session.onWindowSurfaceVisibilityChanged(winSurface, true /* visible */,
- LayoutParams.TYPE_PHONE);
+ final WindowState window = createWindow(null, LayoutParams.TYPE_PHONE, "win");
+ session.onWindowSurfaceVisibilityChanged(window, true /* visible */);
verify(session).setHasOverlayUi(true);
- session.onWindowSurfaceVisibilityChanged(winSurface, false /* visible */,
- LayoutParams.TYPE_PHONE);
+ session.onWindowSurfaceVisibilityChanged(window, false /* visible */);
verify(session).setHasOverlayUi(false);
}
@Test
public void testRelayoutExitingWindow() {
final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, "appWin");
- final WindowSurfaceController surfaceController = mock(WindowSurfaceController.class);
- win.mWinAnimator.mSurfaceController = surfaceController;
win.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN;
- doReturn(true).when(surfaceController).hasSurface();
+ win.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class);
spyOn(win.mTransitionController);
doReturn(true).when(win.mTransitionController).isShellTransitionsEnabled();
doReturn(true).when(win.mTransitionController).inTransition(
@@ -306,7 +302,7 @@
// and WMS#tryStartExitingAnimation() will destroy the surface directly.
assertFalse(win.mAnimatingExit);
assertFalse(win.mHasSurface);
- assertNull(win.mWinAnimator.mSurfaceController);
+ assertNull(win.mWinAnimator.mSurfaceControl);
// Invisible requested activity should not get the last config even if its view is visible.
mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.VISIBLE, 0, 0, 0,
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
index f385179..5f9a710 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
@@ -54,6 +54,7 @@
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -110,6 +111,7 @@
}
@Test
+ @Ignore("b/352823913")
public void passengerShowImeNotAffectDriver() throws Exception {
assertDriverImeHidden();
assertPassengerImeHidden();
diff --git a/tools/lint/fix/README.md b/tools/lint/fix/README.md
index a5ac2be..18bda92 100644
--- a/tools/lint/fix/README.md
+++ b/tools/lint/fix/README.md
@@ -6,7 +6,7 @@
It's a python script that runs the framework linter,
and then (optionally) copies modified files back into the source tree.\
-Why python, you ask? Because python is cool ¯\_(ツ)_/¯.
+Why python, you ask? Because python is cool ¯\\\_(ツ)\_/¯.
Incidentally, this exposes a much simpler way to run individual lint checks
against individual modules, so it's useful beyond applying fixes.
@@ -15,7 +15,7 @@
Lint is not allowed to modify source files directly via lint's `--apply-suggestions` flag.
As a compromise, soong zips up the (potentially) modified sources and leaves them in an intermediate
-directory. This script runs the lint, unpacks those files, and copies them back into the tree.
+directory. This script runs the lint, unpacks those files, and copies them back into the tree.
## How do I run it?
**WARNING: You probably want to commit/stash any changes to your working tree before doing this...**