Merge "Replace default cross activity runner in registerAnimation." into main
diff --git a/libs/hostgraphics/ANativeWindow.cpp b/libs/hostgraphics/ANativeWindow.cpp
new file mode 100644
index 0000000..fcfaf02
--- /dev/null
+++ b/libs/hostgraphics/ANativeWindow.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <system/window.h>
+
+static int32_t query(ANativeWindow* window, int what) {
+    int value;
+    int res = window->query(window, what, &value);
+    return res < 0 ? res : value;
+}
+
+static int64_t query64(ANativeWindow* window, int what) {
+    int64_t value;
+    int res = window->perform(window, what, &value);
+    return res < 0 ? res : value;
+}
+
+int ANativeWindow_setCancelBufferInterceptor(ANativeWindow* window,
+                                             ANativeWindow_cancelBufferInterceptor interceptor,
+                                             void* data) {
+    return window->perform(window, NATIVE_WINDOW_SET_CANCEL_INTERCEPTOR, interceptor, data);
+}
+
+int ANativeWindow_setDequeueBufferInterceptor(ANativeWindow* window,
+                                              ANativeWindow_dequeueBufferInterceptor interceptor,
+                                              void* data) {
+    return window->perform(window, NATIVE_WINDOW_SET_DEQUEUE_INTERCEPTOR, interceptor, data);
+}
+
+int ANativeWindow_setQueueBufferInterceptor(ANativeWindow* window,
+                                            ANativeWindow_queueBufferInterceptor interceptor,
+                                            void* data) {
+    return window->perform(window, NATIVE_WINDOW_SET_QUEUE_INTERCEPTOR, interceptor, data);
+}
+
+int ANativeWindow_setPerformInterceptor(ANativeWindow* window,
+                                        ANativeWindow_performInterceptor interceptor, void* data) {
+    return window->perform(window, NATIVE_WINDOW_SET_PERFORM_INTERCEPTOR, interceptor, data);
+}
+
+int ANativeWindow_dequeueBuffer(ANativeWindow* window, ANativeWindowBuffer** buffer, int* fenceFd) {
+    return window->dequeueBuffer(window, buffer, fenceFd);
+}
+
+int ANativeWindow_cancelBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, int fenceFd) {
+    return window->cancelBuffer(window, buffer, fenceFd);
+}
+
+int ANativeWindow_setDequeueTimeout(ANativeWindow* window, int64_t timeout) {
+    return window->perform(window, NATIVE_WINDOW_SET_DEQUEUE_TIMEOUT, timeout);
+}
+
+// extern "C", so that it can be used outside libhostgraphics (in host hwui/.../CanvasContext.cpp)
+extern "C" void ANativeWindow_tryAllocateBuffers(ANativeWindow* window) {
+    if (!window || !query(window, NATIVE_WINDOW_IS_VALID)) {
+        return;
+    }
+    window->perform(window, NATIVE_WINDOW_ALLOCATE_BUFFERS);
+}
+
+int64_t ANativeWindow_getLastDequeueStartTime(ANativeWindow* window) {
+    return query64(window, NATIVE_WINDOW_GET_LAST_DEQUEUE_START);
+}
+
+int64_t ANativeWindow_getLastDequeueDuration(ANativeWindow* window) {
+    return query64(window, NATIVE_WINDOW_GET_LAST_DEQUEUE_DURATION);
+}
+
+int64_t ANativeWindow_getLastQueueDuration(ANativeWindow* window) {
+    return query64(window, NATIVE_WINDOW_GET_LAST_QUEUE_DURATION);
+}
+
+int32_t ANativeWindow_getWidth(ANativeWindow* window) {
+    return query(window, NATIVE_WINDOW_WIDTH);
+}
+
+int32_t ANativeWindow_getHeight(ANativeWindow* window) {
+    return query(window, NATIVE_WINDOW_HEIGHT);
+}
+
+int32_t ANativeWindow_getFormat(ANativeWindow* window) {
+    return query(window, NATIVE_WINDOW_FORMAT);
+}
+
+void ANativeWindow_acquire(ANativeWindow* window) {
+    // incStrong/decStrong token must be the same, doesn't matter what it is
+    window->incStrong((void*)ANativeWindow_acquire);
+}
+
+void ANativeWindow_release(ANativeWindow* window) {
+    // incStrong/decStrong token must be the same, doesn't matter what it is
+    window->decStrong((void*)ANativeWindow_acquire);
+}
diff --git a/libs/hostgraphics/Android.bp b/libs/hostgraphics/Android.bp
index 4407af6..09232b6 100644
--- a/libs/hostgraphics/Android.bp
+++ b/libs/hostgraphics/Android.bp
@@ -17,26 +17,18 @@
     static_libs: [
         "libbase",
         "libmath",
+        "libui-types",
         "libutils",
     ],
 
     srcs: [
-        ":libui_host_common",
         "ADisplay.cpp",
+        "ANativeWindow.cpp",
         "Fence.cpp",
         "HostBufferQueue.cpp",
         "PublicFormat.cpp",
     ],
 
-    include_dirs: [
-        // Here we override all the headers automatically included with frameworks/native/include.
-        // When frameworks/native/include will be removed from the list of automatic includes.
-        // We will have to copy necessary headers with a pre-build step (generated headers).
-        ".",
-        "frameworks/native/libs/arect/include",
-        "frameworks/native/libs/ui/include_private",
-    ],
-
     header_libs: [
         "libnativebase_headers",
         "libnativedisplay_headers",
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 4706699..7439fbc 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -96,6 +96,7 @@
             ],
             cflags: [
                 "-Wno-unused-variable",
+                "-D__INTRODUCED_IN(n)=",
             ],
         },
     },
@@ -539,7 +540,10 @@
         "pipeline/skia/ReorderBarrierDrawables.cpp",
         "pipeline/skia/TransformCanvas.cpp",
         "renderstate/RenderState.cpp",
+        "renderthread/CanvasContext.cpp",
+        "renderthread/DrawFrameTask.cpp",
         "renderthread/Frame.cpp",
+        "renderthread/RenderProxy.cpp",
         "renderthread/RenderTask.cpp",
         "renderthread/TimeLord.cpp",
         "hwui/AnimatedImageDrawable.cpp",
@@ -589,6 +593,7 @@
         "SkiaCanvas.cpp",
         "SkiaInterpolator.cpp",
         "Tonemapper.cpp",
+        "TreeInfo.cpp",
         "VectorDrawable.cpp",
     ],
 
@@ -617,14 +622,11 @@
                 "pipeline/skia/VkFunctorDrawable.cpp",
                 "pipeline/skia/VkInteropFunctorDrawable.cpp",
                 "renderthread/CacheManager.cpp",
-                "renderthread/CanvasContext.cpp",
-                "renderthread/DrawFrameTask.cpp",
                 "renderthread/EglManager.cpp",
                 "renderthread/ReliableSurface.cpp",
                 "renderthread/RenderEffectCapabilityQuery.cpp",
                 "renderthread/VulkanManager.cpp",
                 "renderthread/VulkanSurface.cpp",
-                "renderthread/RenderProxy.cpp",
                 "renderthread/RenderThread.cpp",
                 "renderthread/HintSessionWrapper.cpp",
                 "service/GraphicsStatsService.cpp",
@@ -636,7 +638,6 @@
                 "Layer.cpp",
                 "ProfileDataContainer.cpp",
                 "Readback.cpp",
-                "TreeInfo.cpp",
                 "WebViewFunctorManager.cpp",
                 "protos/graphicsstats.proto",
             ],
@@ -654,6 +655,8 @@
 
             srcs: [
                 "platform/host/renderthread/CacheManager.cpp",
+                "platform/host/renderthread/HintSessionWrapper.cpp",
+                "platform/host/renderthread/ReliableSurface.cpp",
                 "platform/host/renderthread/RenderThread.cpp",
                 "platform/host/ProfileDataContainer.cpp",
                 "platform/host/Readback.cpp",
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index f526a28..589abb4 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -16,18 +16,6 @@
 
 #include "RenderNode.h"
 
-#include "DamageAccumulator.h"
-#include "Debug.h"
-#include "Properties.h"
-#include "TreeInfo.h"
-#include "VectorDrawable.h"
-#include "private/hwui/WebViewFunctor.h"
-#ifdef __ANDROID__
-#include "renderthread/CanvasContext.h"
-#else
-#include "DamageAccumulator.h"
-#include "pipeline/skia/SkiaDisplayList.h"
-#endif
 #include <SkPathOps.h>
 #include <gui/TraceUtils.h>
 #include <ui/FatVector.h>
@@ -37,6 +25,14 @@
 #include <sstream>
 #include <string>
 
+#include "DamageAccumulator.h"
+#include "Debug.h"
+#include "Properties.h"
+#include "TreeInfo.h"
+#include "VectorDrawable.h"
+#include "private/hwui/WebViewFunctor.h"
+#include "renderthread/CanvasContext.h"
+
 #ifdef __ANDROID__
 #include "include/gpu/ganesh/SkImageGanesh.h"
 #endif
@@ -186,7 +182,6 @@
 }
 
 void RenderNode::pushLayerUpdate(TreeInfo& info) {
-#ifdef __ANDROID__ // Layoutlib does not support CanvasContext and Layers
     LayerType layerType = properties().effectiveLayerType();
     // If we are not a layer OR we cannot be rendered (eg, view was detached)
     // we need to destroy any Layers we may have had previously
@@ -218,7 +213,6 @@
     // That might be us, so tell CanvasContext that this layer is in the
     // tree and should not be destroyed.
     info.canvasContext.markLayerInUse(this);
-#endif
 }
 
 /**
diff --git a/libs/hwui/RootRenderNode.cpp b/libs/hwui/RootRenderNode.cpp
index ddbbf58..5174e27 100644
--- a/libs/hwui/RootRenderNode.cpp
+++ b/libs/hwui/RootRenderNode.cpp
@@ -18,11 +18,12 @@
 
 #ifdef __ANDROID__ // Layoutlib does not support Looper (windows)
 #include <utils/Looper.h>
+#else
+#include "utils/MessageHandler.h"
 #endif
 
 namespace android::uirenderer {
 
-#ifdef __ANDROID__ // Layoutlib does not support Looper
 class FinishAndInvokeListener : public MessageHandler {
 public:
     explicit FinishAndInvokeListener(PropertyValuesAnimatorSet* anim) : mAnimator(anim) {
@@ -237,9 +238,13 @@
         // user events, in which case the already posted listener's id will become stale, and
         // the onFinished callback will then be ignored.
         sp<FinishAndInvokeListener> message = new FinishAndInvokeListener(anim);
+#ifdef __ANDROID__  // Layoutlib does not support Looper
         auto looper = Looper::getForThread();
         LOG_ALWAYS_FATAL_IF(looper == nullptr, "Not on a looper thread?");
         looper->sendMessageDelayed(ms2ns(remainingTimeInMs), message, 0);
+#else
+        message->handleMessage(0);
+#endif
         anim->clearOneShotListener();
     }
 }
@@ -285,22 +290,5 @@
 AnimationContext* ContextFactoryImpl::createAnimationContext(renderthread::TimeLord& clock) {
     return new AnimationContextBridge(clock, mRootNode);
 }
-#else
-
-void RootRenderNode::prepareTree(TreeInfo& info) {
-    info.errorHandler = mErrorHandler.get();
-    info.updateWindowPositions = true;
-    RenderNode::prepareTree(info);
-    info.updateWindowPositions = false;
-    info.errorHandler = nullptr;
-}
-
-void RootRenderNode::attachAnimatingNode(RenderNode* animatingNode) { }
-
-void RootRenderNode::destroy() { }
-
-void RootRenderNode::addVectorDrawableAnimator(PropertyValuesAnimatorSet* anim) { }
-
-#endif
 
 }  // namespace android::uirenderer
diff --git a/libs/hwui/RootRenderNode.h b/libs/hwui/RootRenderNode.h
index 1d3f5a8..7a5cda7 100644
--- a/libs/hwui/RootRenderNode.h
+++ b/libs/hwui/RootRenderNode.h
@@ -74,7 +74,6 @@
     void detachVectorDrawableAnimator(PropertyValuesAnimatorSet* anim);
 };
 
-#ifdef __ANDROID__ // Layoutlib does not support Animations
 class ContextFactoryImpl : public IContextFactory {
 public:
     explicit ContextFactoryImpl(RootRenderNode* rootNode) : mRootNode(rootNode) {}
@@ -84,6 +83,5 @@
 private:
     RootRenderNode* mRootNode;
 };
-#endif
 
 }  // namespace android::uirenderer
diff --git a/libs/hwui/WebViewFunctorManager.cpp b/libs/hwui/WebViewFunctorManager.cpp
index efa9b11..9d16ee8 100644
--- a/libs/hwui/WebViewFunctorManager.cpp
+++ b/libs/hwui/WebViewFunctorManager.cpp
@@ -87,7 +87,7 @@
     WebViewFunctorManager::instance().releaseFunctor(functor);
 }
 
-void WebViewFunctor_reportRenderingThreads(int functor, const int32_t* thread_ids, size_t size) {
+void WebViewFunctor_reportRenderingThreads(int functor, const pid_t* thread_ids, size_t size) {
     WebViewFunctorManager::instance().reportRenderingThreads(functor, thread_ids, size);
 }
 
@@ -265,8 +265,8 @@
     funcs.transactionDeleteFunc(transaction);
 }
 
-void WebViewFunctor::reportRenderingThreads(const int32_t* thread_ids, size_t size) {
-    mRenderingThreads = std::vector<int32_t>(thread_ids, thread_ids + size);
+void WebViewFunctor::reportRenderingThreads(const pid_t* thread_ids, size_t size) {
+    mRenderingThreads = std::vector<pid_t>(thread_ids, thread_ids + size);
 }
 
 WebViewFunctorManager& WebViewFunctorManager::instance() {
@@ -355,7 +355,7 @@
     }
 }
 
-void WebViewFunctorManager::reportRenderingThreads(int functor, const int32_t* thread_ids,
+void WebViewFunctorManager::reportRenderingThreads(int functor, const pid_t* thread_ids,
                                                    size_t size) {
     std::lock_guard _lock{mLock};
     for (auto& iter : mFunctors) {
@@ -366,8 +366,8 @@
     }
 }
 
-std::vector<int32_t> WebViewFunctorManager::getRenderingThreadsForActiveFunctors() {
-    std::vector<int32_t> renderingThreads;
+std::vector<pid_t> WebViewFunctorManager::getRenderingThreadsForActiveFunctors() {
+    std::vector<pid_t> renderingThreads;
     std::lock_guard _lock{mLock};
     for (const auto& iter : mActiveFunctors) {
         const auto& functorThreads = iter->getRenderingThreads();
diff --git a/libs/hwui/WebViewFunctorManager.h b/libs/hwui/WebViewFunctorManager.h
index 2d77dd8..ec17640 100644
--- a/libs/hwui/WebViewFunctorManager.h
+++ b/libs/hwui/WebViewFunctorManager.h
@@ -17,13 +17,11 @@
 #pragma once
 
 #include <private/hwui/WebViewFunctor.h>
-#ifdef __ANDROID__ // Layoutlib does not support render thread
 #include <renderthread/RenderProxy.h>
-#endif
-
 #include <utils/LightRefBase.h>
 #include <utils/Log.h>
 #include <utils/StrongPointer.h>
+
 #include <mutex>
 #include <vector>
 
@@ -38,11 +36,7 @@
 
     class Handle : public LightRefBase<Handle> {
     public:
-        ~Handle() {
-#ifdef __ANDROID__ // Layoutlib does not support render thread
-            renderthread::RenderProxy::destroyFunctor(id());
-#endif
-        }
+        ~Handle() { renderthread::RenderProxy::destroyFunctor(id()); }
 
         int id() const { return mReference.id(); }
 
@@ -60,7 +54,7 @@
 
         void onRemovedFromTree() { mReference.onRemovedFromTree(); }
 
-        const std::vector<int32_t>& getRenderingThreads() const {
+        const std::vector<pid_t>& getRenderingThreads() const {
             return mReference.getRenderingThreads();
         }
 
@@ -85,8 +79,8 @@
     ASurfaceControl* getSurfaceControl();
     void mergeTransaction(ASurfaceTransaction* transaction);
 
-    void reportRenderingThreads(const int32_t* thread_ids, size_t size);
-    const std::vector<int32_t>& getRenderingThreads() const { return mRenderingThreads; }
+    void reportRenderingThreads(const pid_t* thread_ids, size_t size);
+    const std::vector<pid_t>& getRenderingThreads() const { return mRenderingThreads; }
 
     sp<Handle> createHandle() {
         LOG_ALWAYS_FATAL_IF(mCreatedHandle);
@@ -107,7 +101,7 @@
     bool mCreatedHandle = false;
     int32_t mParentSurfaceControlGenerationId = 0;
     ASurfaceControl* mSurfaceControl = nullptr;
-    std::vector<int32_t> mRenderingThreads;
+    std::vector<pid_t> mRenderingThreads;
 };
 
 class WebViewFunctorManager {
@@ -118,8 +112,8 @@
     void releaseFunctor(int functor);
     void onContextDestroyed();
     void destroyFunctor(int functor);
-    void reportRenderingThreads(int functor, const int32_t* thread_ids, size_t size);
-    std::vector<int32_t> getRenderingThreadsForActiveFunctors();
+    void reportRenderingThreads(int functor, const pid_t* thread_ids, size_t size);
+    std::vector<pid_t> getRenderingThreadsForActiveFunctors();
 
     sp<WebViewFunctor::Handle> handleFor(int functor);
 
diff --git a/libs/hwui/apex/LayoutlibLoader.cpp b/libs/hwui/apex/LayoutlibLoader.cpp
index 770822a..fd9915a 100644
--- a/libs/hwui/apex/LayoutlibLoader.cpp
+++ b/libs/hwui/apex/LayoutlibLoader.cpp
@@ -164,8 +164,10 @@
 } // namespace android
 
 using namespace android;
+using namespace android::uirenderer;
 
 void init_android_graphics() {
+    Properties::overrideRenderPipelineType(RenderPipelineType::SkiaCpu);
     SkGraphics::Init();
 }
 
diff --git a/libs/hwui/jni/AnimatedImageDrawable.cpp b/libs/hwui/jni/AnimatedImageDrawable.cpp
index 0f80c55..b01e38d 100644
--- a/libs/hwui/jni/AnimatedImageDrawable.cpp
+++ b/libs/hwui/jni/AnimatedImageDrawable.cpp
@@ -27,6 +27,8 @@
 #include <hwui/ImageDecoder.h>
 #ifdef __ANDROID__
 #include <utils/Looper.h>
+#else
+#include "utils/MessageHandler.h"
 #endif
 
 #include "ColorFilter.h"
@@ -182,23 +184,6 @@
     drawable->setRepetitionCount(loopCount);
 }
 
-#ifndef __ANDROID__
-struct Message {
-    Message(int w) {}
-};
-
-class MessageHandler : public virtual RefBase {
-protected:
-    virtual ~MessageHandler() override {}
-
-public:
-    /**
-     * Handles a message.
-     */
-    virtual void handleMessage(const Message& message) = 0;
-};
-#endif
-
 class InvokeListener : public MessageHandler {
 public:
     InvokeListener(JNIEnv* env, jobject javaObject) {
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index 9e21f86..d415700 100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -1,8 +1,14 @@
 // #define LOG_NDEBUG 0
 #include "Bitmap.h"
 
+#include <android-base/unique_fd.h>
 #include <hwui/Bitmap.h>
 #include <hwui/Paint.h>
+#include <inttypes.h>
+#include <renderthread/RenderProxy.h>
+#include <string.h>
+
+#include <memory>
 
 #include "CreateJavaOutputStreamAdaptor.h"
 #include "Gainmap.h"
@@ -24,16 +30,6 @@
 #include "SkTypes.h"
 #include "android_nio_utils.h"
 
-#ifdef __ANDROID__ // Layoutlib does not support graphic buffer, parcel or render thread
-#include <android-base/unique_fd.h>
-#include <renderthread/RenderProxy.h>
-#endif
-
-#include <inttypes.h>
-#include <string.h>
-
-#include <memory>
-
 #define DEBUG_PARCEL 0
 
 static jclass   gBitmap_class;
@@ -1105,11 +1101,9 @@
 }
 
 static void Bitmap_prepareToDraw(JNIEnv* env, jobject, jlong bitmapPtr) {
-#ifdef __ANDROID__ // Layoutlib does not support render thread
     LocalScopedBitmap bitmapHandle(bitmapPtr);
     if (!bitmapHandle.valid()) return;
     android::uirenderer::renderthread::RenderProxy::prepareToDraw(bitmapHandle->bitmap());
-#endif
 }
 
 static jint Bitmap_getAllocationByteCount(JNIEnv* env, jobject, jlong bitmapPtr) {
diff --git a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
index 426644e..948362c 100644
--- a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
+++ b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
@@ -16,22 +16,19 @@
 
 #include "GraphicsJNI.h"
 
-#ifdef __ANDROID__ // Layoutlib does not support Looper and device properties
+#ifdef __ANDROID__  // Layoutlib does not support Looper
 #include <utils/Looper.h>
 #endif
 
-#include <SkRegion.h>
-#include <SkRuntimeEffect.h>
-
+#include <CanvasProperty.h>
 #include <Rect.h>
 #include <RenderNode.h>
-#include <CanvasProperty.h>
+#include <SkRegion.h>
+#include <SkRuntimeEffect.h>
 #include <hwui/Canvas.h>
 #include <hwui/Paint.h>
 #include <minikin/Layout.h>
-#ifdef __ANDROID__ // Layoutlib does not support RenderThread
 #include <renderthread/RenderProxy.h>
-#endif
 
 namespace android {
 
@@ -85,11 +82,7 @@
 }
 
 static jint android_view_DisplayListCanvas_getMaxTextureSize(JNIEnv*, jobject) {
-#ifdef __ANDROID__ // Layoutlib does not support RenderProxy (RenderThread)
     return android::uirenderer::renderthread::RenderProxy::maxTextureSize();
-#else
-    return 4096;
-#endif
 }
 
 static void android_view_DisplayListCanvas_enableZ(CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr,
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index a7d6423..6e03bbd 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -15,19 +15,17 @@
  */
 
 #define ATRACE_TAG ATRACE_TAG_VIEW
-#include "GraphicsJNI.h"
-
 #include <Animator.h>
 #include <DamageAccumulator.h>
 #include <Matrix.h>
 #include <RenderNode.h>
-#ifdef __ANDROID__ // Layoutlib does not support CanvasContext
-#include <renderthread/CanvasContext.h>
-#endif
 #include <TreeInfo.h>
 #include <effects/StretchEffect.h>
 #include <gui/TraceUtils.h>
 #include <hwui/Paint.h>
+#include <renderthread/CanvasContext.h>
+
+#include "GraphicsJNI.h"
 
 namespace android {
 
@@ -640,7 +638,6 @@
 
             ATRACE_NAME("Update SurfaceView position");
 
-#ifdef __ANDROID__ // Layoutlib does not support CanvasContext
             JNIEnv* env = jnienv();
             // Update the new position synchronously. We cannot defer this to
             // a worker pool to process asynchronously because the UI thread
@@ -669,7 +666,6 @@
                 env->DeleteGlobalRef(mListener);
                 mListener = nullptr;
             }
-#endif
         }
 
         virtual void onPositionLost(RenderNode& node, const TreeInfo* info) override {
@@ -682,7 +678,6 @@
 
             ATRACE_NAME("SurfaceView position lost");
             JNIEnv* env = jnienv();
-#ifdef __ANDROID__ // Layoutlib does not support CanvasContext
             // Update the lost position synchronously. We cannot defer this to
             // a worker pool to process asynchronously because the UI thread
             // may be unblocked by the time a worker thread can process this,
@@ -698,7 +693,6 @@
                 env->DeleteGlobalRef(mListener);
                 mListener = nullptr;
             }
-#endif
         }
 
     private:
@@ -750,7 +744,6 @@
                 StretchEffectBehavior::Shader) {
                 JNIEnv* env = jnienv();
 
-#ifdef __ANDROID__  // Layoutlib does not support CanvasContext
                 SkVector stretchDirection = effect->getStretchDirection();
                 jboolean keepListening = env->CallStaticBooleanMethod(
                         gPositionListener.clazz, gPositionListener.callApplyStretch, mListener,
@@ -762,7 +755,6 @@
                     env->DeleteGlobalRef(mListener);
                     mListener = nullptr;
                 }
-#endif
             }
         }
 
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index e0216b6..36dc933 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -15,23 +15,19 @@
  */
 
 #include "SkiaDisplayList.h"
-#include "FunctorDrawable.h"
-
-#include "DumpOpsCanvas.h"
-#ifdef __ANDROID__ // Layoutlib does not support SkiaPipeline
-#include "SkiaPipeline.h"
-#else
-#include "DamageAccumulator.h"
-#endif
-#include "TreeInfo.h"
-#include "VectorDrawable.h"
-#ifdef __ANDROID__
-#include "renderthread/CanvasContext.h"
-#endif
 
 #include <SkImagePriv.h>
 #include <SkPathOps.h>
 
+// clang-format off
+#include "FunctorDrawable.h" // Must be included before DumpOpsCanvas.h
+#include "DumpOpsCanvas.h"
+// clang-format on
+#include "SkiaPipeline.h"
+#include "TreeInfo.h"
+#include "VectorDrawable.h"
+#include "renderthread/CanvasContext.h"
+
 namespace android {
 namespace uirenderer {
 namespace skiapipeline {
@@ -101,7 +97,6 @@
     // If the prepare tree is triggered by the UI thread and no previous call to
     // pinImages has failed then we must pin all mutable images in the GPU cache
     // until the next UI thread draw.
-#ifdef __ANDROID__ // Layoutlib does not support CanvasContext
     if (info.prepareTextures && !info.canvasContext.pinImages(mMutableImages)) {
         // In the event that pinning failed we prevent future pinImage calls for the
         // remainder of this tree traversal and also unpin any currently pinned images
@@ -110,11 +105,11 @@
         info.canvasContext.unpinImages();
     }
 
+#ifdef __ANDROID__
     auto grContext = info.canvasContext.getGrContext();
     for (const auto& bufferData : mMeshBufferData) {
         bufferData->updateBuffers(grContext);
     }
-
 #endif
 
     bool hasBackwardProjectedNodesHere = false;
diff --git a/libs/hwui/platform/host/WebViewFunctorManager.cpp b/libs/hwui/platform/host/WebViewFunctorManager.cpp
index 1d16655..4ba206b 100644
--- a/libs/hwui/platform/host/WebViewFunctorManager.cpp
+++ b/libs/hwui/platform/host/WebViewFunctorManager.cpp
@@ -50,6 +50,8 @@
 
 void WebViewFunctor::mergeTransaction(ASurfaceTransaction* transaction) {}
 
+void WebViewFunctor::reportRenderingThreads(const pid_t* thread_ids, size_t size) {}
+
 void WebViewFunctor::reparentSurfaceControl(ASurfaceControl* parent) {}
 
 WebViewFunctorManager& WebViewFunctorManager::instance() {
@@ -68,6 +70,13 @@
 
 void WebViewFunctorManager::destroyFunctor(int functor) {}
 
+void WebViewFunctorManager::reportRenderingThreads(int functor, const pid_t* thread_ids,
+                                                   size_t size) {}
+
+std::vector<pid_t> WebViewFunctorManager::getRenderingThreadsForActiveFunctors() {
+    return {};
+}
+
 sp<WebViewFunctor::Handle> WebViewFunctorManager::handleFor(int functor) {
     return nullptr;
 }
diff --git a/libs/hwui/platform/host/renderthread/HintSessionWrapper.cpp b/libs/hwui/platform/host/renderthread/HintSessionWrapper.cpp
new file mode 100644
index 0000000..b1b1d58
--- /dev/null
+++ b/libs/hwui/platform/host/renderthread/HintSessionWrapper.cpp
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+#include "renderthread/HintSessionWrapper.h"
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+void HintSessionWrapper::HintSessionBinding::init() {}
+
+HintSessionWrapper::HintSessionWrapper(pid_t uiThreadId, pid_t renderThreadId)
+        : mUiThreadId(uiThreadId)
+        , mRenderThreadId(renderThreadId)
+        , mBinding(std::make_shared<HintSessionBinding>()) {}
+
+HintSessionWrapper::~HintSessionWrapper() {}
+
+void HintSessionWrapper::destroy() {}
+
+bool HintSessionWrapper::init() {
+    return false;
+}
+
+void HintSessionWrapper::updateTargetWorkDuration(long targetWorkDurationNanos) {}
+
+void HintSessionWrapper::reportActualWorkDuration(long actualDurationNanos) {}
+
+void HintSessionWrapper::sendLoadResetHint() {}
+
+void HintSessionWrapper::sendLoadIncreaseHint() {}
+
+bool HintSessionWrapper::alive() {
+    return false;
+}
+
+nsecs_t HintSessionWrapper::getLastUpdate() {
+    return -1;
+}
+
+void HintSessionWrapper::delayedDestroy(RenderThread& rt, nsecs_t delay,
+                                        std::shared_ptr<HintSessionWrapper> wrapperPtr) {}
+
+void HintSessionWrapper::setActiveFunctorThreads(std::vector<pid_t> threadIds) {}
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/platform/host/renderthread/ReliableSurface.cpp b/libs/hwui/platform/host/renderthread/ReliableSurface.cpp
new file mode 100644
index 0000000..2deaaf3
--- /dev/null
+++ b/libs/hwui/platform/host/renderthread/ReliableSurface.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "renderthread/ReliableSurface.h"
+
+#include <log/log_main.h>
+#include <system/window.h>
+
+namespace android::uirenderer::renderthread {
+
+ReliableSurface::ReliableSurface(ANativeWindow* window) : mWindow(window) {
+    LOG_ALWAYS_FATAL_IF(!mWindow, "Error, unable to wrap a nullptr");
+    ANativeWindow_acquire(mWindow);
+}
+
+ReliableSurface::~ReliableSurface() {
+    ANativeWindow_release(mWindow);
+}
+
+void ReliableSurface::init() {}
+
+int ReliableSurface::reserveNext() {
+    return OK;
+}
+
+};  // namespace android::uirenderer::renderthread
diff --git a/libs/hwui/platform/host/utils/MessageHandler.h b/libs/hwui/platform/host/utils/MessageHandler.h
new file mode 100644
index 0000000..51ee48e
--- /dev/null
+++ b/libs/hwui/platform/host/utils/MessageHandler.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <utils/RefBase.h>
+
+struct Message {
+    Message(int w) {}
+};
+
+class MessageHandler : public virtual android::RefBase {
+protected:
+    virtual ~MessageHandler() override {}
+
+public:
+    /**
+     * Handles a message.
+     */
+    virtual void handleMessage(const Message& message) = 0;
+};
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 22de2f2..66e0896 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -35,6 +35,7 @@
 #include "Properties.h"
 #include "RenderThread.h"
 #include "hwui/Canvas.h"
+#include "pipeline/skia/SkiaCpuPipeline.h"
 #include "pipeline/skia/SkiaGpuPipeline.h"
 #include "pipeline/skia/SkiaOpenGLPipeline.h"
 #include "pipeline/skia/SkiaVulkanPipeline.h"
@@ -72,7 +73,7 @@
 
 CanvasContext* CanvasContext::create(RenderThread& thread, bool translucent,
                                      RenderNode* rootRenderNode, IContextFactory* contextFactory,
-                                     int32_t uiThreadId, int32_t renderThreadId) {
+                                     pid_t uiThreadId, pid_t renderThreadId) {
     auto renderType = Properties::getRenderPipelineType();
 
     switch (renderType) {
@@ -84,6 +85,12 @@
             return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
                                      std::make_unique<skiapipeline::SkiaVulkanPipeline>(thread),
                                      uiThreadId, renderThreadId);
+#ifndef __ANDROID__
+        case RenderPipelineType::SkiaCpu:
+            return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
+                                     std::make_unique<skiapipeline::SkiaCpuPipeline>(thread),
+                                     uiThreadId, renderThreadId);
+#endif
         default:
             LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t)renderType);
             break;
@@ -182,6 +189,7 @@
 }
 
 void CanvasContext::setHardwareBuffer(AHardwareBuffer* buffer) {
+#ifdef __ANDROID__
     if (mHardwareBuffer) {
         AHardwareBuffer_release(mHardwareBuffer);
         mHardwareBuffer = nullptr;
@@ -192,6 +200,7 @@
         mHardwareBuffer = buffer;
     }
     mRenderPipeline->setHardwareBuffer(mHardwareBuffer);
+#endif
 }
 
 void CanvasContext::setSurface(ANativeWindow* window, bool enableTimeout) {
@@ -561,6 +570,7 @@
 }
 
 void CanvasContext::draw(bool solelyTextureViewUpdates) {
+#ifdef __ANDROID__
     if (auto grContext = getGrContext()) {
         if (grContext->abandoned()) {
             if (grContext->isDeviceLost()) {
@@ -571,6 +581,7 @@
             return;
         }
     }
+#endif
     SkRect dirty;
     mDamageAccumulator.finish(&dirty);
 
@@ -594,11 +605,13 @@
     if (skippedFrameReason) {
         mCurrentFrameInfo->setSkippedFrameReason(*skippedFrameReason);
 
+#ifdef __ANDROID__
         if (auto grContext = getGrContext()) {
             // Submit to ensure that any texture uploads complete and Skia can
             // free its staging buffers.
             grContext->flushAndSubmit();
         }
+#endif
 
         // Notify the callbacks, even if there's nothing to draw so they aren't waiting
         // indefinitely
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 1b333bf..826d00e 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -140,12 +140,14 @@
     if (CC_LIKELY(canDrawThisFrame)) {
         context->draw(solelyTextureViewUpdates);
     } else {
+#ifdef __ANDROID__
         // Do a flush in case syncFrameState performed any texture uploads. Since we skipped
         // the draw() call, those uploads (or deletes) will end up sitting in the queue.
         // Do them now
         if (GrDirectContext* grContext = mRenderThread->getGrContext()) {
             grContext->flushAndSubmit();
         }
+#endif
         // wait on fences so tasks don't overlap next frame
         context->waitOnFences();
     }
@@ -176,11 +178,13 @@
     bool canDraw = mContext->makeCurrent();
     mContext->unpinImages();
 
+#ifdef __ANDROID__
     for (size_t i = 0; i < mLayers.size(); i++) {
         if (mLayers[i]) {
             mLayers[i]->apply();
         }
     }
+#endif
 
     mLayers.clear();
     mContext->setContentDrawBounds(mContentDrawBounds);
diff --git a/libs/hwui/renderthread/ReliableSurface.h b/libs/hwui/renderthread/ReliableSurface.h
index 5959647..d6a4d50 100644
--- a/libs/hwui/renderthread/ReliableSurface.h
+++ b/libs/hwui/renderthread/ReliableSurface.h
@@ -21,7 +21,9 @@
 #include <apex/window.h>
 #include <utils/Errors.h>
 #include <utils/Macros.h>
+#ifdef __ANDROID__
 #include <utils/NdkUtils.h>
+#endif
 #include <utils/StrongPointer.h>
 
 #include <memory>
@@ -62,9 +64,11 @@
     mutable std::mutex mMutex;
 
     uint64_t mUsage = AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER;
+#ifdef __ANDROID__
     AHardwareBuffer_Format mFormat = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
     UniqueAHardwareBuffer mScratchBuffer;
     ANativeWindowBuffer* mReservedBuffer = nullptr;
+#endif
     base::unique_fd mReservedFenceFd;
     bool mHasDequeuedBuffer = false;
     int mBufferQueueState = OK;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index eab3605..715153b 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -42,7 +42,11 @@
 RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode,
                          IContextFactory* contextFactory)
         : mRenderThread(RenderThread::getInstance()), mContext(nullptr) {
+#ifdef __ANDROID__
     pid_t uiThreadId = pthread_gettid_np(pthread_self());
+#else
+    pid_t uiThreadId = 0;
+#endif
     pid_t renderThreadId = getRenderThreadTid();
     mContext = mRenderThread.queue().runSync([=, this]() -> CanvasContext* {
         CanvasContext* context = CanvasContext::create(mRenderThread, translucent, rootRenderNode,
@@ -90,6 +94,7 @@
 }
 
 void RenderProxy::setHardwareBuffer(AHardwareBuffer* buffer) {
+#ifdef __ANDROID__
     if (buffer) {
         AHardwareBuffer_acquire(buffer);
     }
@@ -99,6 +104,7 @@
             AHardwareBuffer_release(hardwareBuffer);
         }
     });
+#endif
 }
 
 void RenderProxy::setSurface(ANativeWindow* window, bool enableTimeout) {
@@ -216,7 +222,9 @@
 }
 
 void RenderProxy::detachSurfaceTexture(DeferredLayerUpdater* layer) {
+#ifdef __ANDROID__
     return mRenderThread.queue().runSync([&]() { layer->detachSurfaceTexture(); });
+#endif
 }
 
 void RenderProxy::destroyHardwareResources() {
@@ -324,11 +332,13 @@
             }
         });
     }
+#ifdef __ANDROID__
     if (!Properties::isolatedProcess) {
         std::string grallocInfo;
         GraphicBufferAllocator::getInstance().dump(grallocInfo);
         dprintf(fd, "%s\n", grallocInfo.c_str());
     }
+#endif
 }
 
 void RenderProxy::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {
@@ -352,7 +362,11 @@
 }
 
 int RenderProxy::getRenderThreadTid() {
+#ifdef __ANDROID__
     return mRenderThread.getTid();
+#else
+    return 0;
+#endif
 }
 
 void RenderProxy::addRenderNode(RenderNode* node, bool placeFront) {
@@ -461,7 +475,7 @@
 int RenderProxy::copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap) {
     ATRACE_NAME("HardwareBitmap readback");
     RenderThread& thread = RenderThread::getInstance();
-    if (gettid() == thread.getTid()) {
+    if (RenderThread::isCurrent()) {
         // TODO: fix everything that hits this. We should never be triggering a readback ourselves.
         return (int)thread.readback().copyHWBitmapInto(hwBitmap, bitmap);
     } else {
@@ -472,7 +486,7 @@
 
 int RenderProxy::copyImageInto(const sk_sp<SkImage>& image, SkBitmap* bitmap) {
     RenderThread& thread = RenderThread::getInstance();
-    if (gettid() == thread.getTid()) {
+    if (RenderThread::isCurrent()) {
         // TODO: fix everything that hits this. We should never be triggering a readback ourselves.
         return (int)thread.readback().copyImageInto(image, bitmap);
     } else {
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index 207ccbe..871e9ab 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -80,4 +80,7 @@
     boolean hasCustomMediaSessionPolicyProvider(String componentName);
     int getSessionPolicies(in MediaSession.Token token);
     void setSessionPolicies(in MediaSession.Token token, int policies);
+
+    // For testing of temporarily engaged sessions.
+    void expireTempEngagedSessions();
 }
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index 0ee9d59..85ad160 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,7 +15,7 @@
 #
 
 [versions]
-agp = "8.3.1"
+agp = "8.3.2"
 compose-compiler = "1.5.11"
 dexmaker-mockito = "2.28.3"
 jvm = "17"
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.6-bin.zip b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.6-bin.zip
deleted file mode 100644
index 5c96347..0000000
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.6-bin.zip
+++ /dev/null
Binary files differ
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.7-bin.zip b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.7-bin.zip
new file mode 100644
index 0000000..7a9ac5a
--- /dev/null
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.7-bin.zip
Binary files differ
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
index 50ff9df..182095e 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
@@ -16,6 +16,6 @@
 
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=gradle-8.6-bin.zip
+distributionUrl=gradle-8.7-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index 2f2ac24..6344501 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -65,7 +65,7 @@
     api("androidx.lifecycle:lifecycle-runtime-compose")
     api("androidx.navigation:navigation-compose:2.8.0-alpha05")
     api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
-    api("com.google.android.material:material:1.7.0-alpha03")
+    api("com.google.android.material:material:1.11.0")
     debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
     implementation("com.airbnb.android:lottie-compose:5.2.0")
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
index 2af042a..e1ee01e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
@@ -28,11 +28,13 @@
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.style.TextAlign
 import androidx.slice.Slice
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.animation.Expandable
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.volume.panel.component.anc.ui.viewmodel.AncViewModel
 import com.android.systemui.volume.panel.component.popup.ui.composable.VolumePanelPopup
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
 import javax.inject.Inject
 
 /** ANC popup up displaying ANC control [Slice]. */
@@ -41,10 +43,12 @@
 constructor(
     private val volumePanelPopup: VolumePanelPopup,
     private val viewModel: AncViewModel,
+    private val uiEventLogger: UiEventLogger,
 ) {
 
     /** Shows a popup with the [expandable] animation. */
     fun show(expandable: Expandable?) {
+        uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_ANC_POPUP_SHOWN)
         volumePanelPopup.show(expandable, { Title() }, { Content(it) })
     }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
index eed54da..9a98bde 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
@@ -26,6 +26,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.style.TextAlign
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.ui.compose.Icon
 import com.android.systemui.common.ui.compose.toColor
@@ -34,6 +35,7 @@
 import com.android.systemui.volume.panel.component.popup.ui.composable.VolumePanelPopup
 import com.android.systemui.volume.panel.component.selector.ui.composable.VolumePanelRadioButtonBar
 import com.android.systemui.volume.panel.component.spatial.ui.viewmodel.SpatialAudioViewModel
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
 import javax.inject.Inject
 
 class SpatialAudioPopup
@@ -41,10 +43,17 @@
 constructor(
     private val viewModel: SpatialAudioViewModel,
     private val volumePanelPopup: VolumePanelPopup,
+    private val uiEventLogger: UiEventLogger,
 ) {
 
     /** Shows a popup with the [expandable] animation. */
     fun show(expandable: Expandable) {
+        uiEventLogger.logWithPosition(
+            VolumePanelUiEvent.VOLUME_PANEL_SPATIAL_AUDIO_POP_UP_SHOWN,
+            0,
+            null,
+            viewModel.spatialAudioButtons.value.indexOfFirst { it.button.isChecked }
+        )
         volumePanelPopup.show(expandable, { Title() }, { Content(it) })
     }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
index f89669c..a54d005 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
@@ -85,6 +85,7 @@
                 onValueChange = { newValue: Float ->
                     sliderViewModel.onValueChanged(sliderState, newValue)
                 },
+                onValueChangeFinished = { sliderViewModel.onValueChangeFinished() },
                 onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
                 sliderColors = sliderColors,
             )
@@ -131,6 +132,7 @@
                                 onValueChange = { newValue: Float ->
                                     sliderViewModel.onValueChanged(sliderState, newValue)
                                 },
+                                onValueChangeFinished = { sliderViewModel.onValueChangeFinished() },
                                 onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
                                 sliderColors = sliderColors,
                             )
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
index b284c69..bb17499 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
@@ -46,6 +46,7 @@
                 onValueChange = { newValue: Float ->
                     sliderViewModel.onValueChanged(sliderState, newValue)
                 },
+                onValueChangeFinished = { sliderViewModel.onValueChangeFinished() },
                 onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
                 sliderColors = sliderColors,
             )
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index 28cd37e..228d292 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -51,6 +51,7 @@
 fun VolumeSlider(
     state: SliderState,
     onValueChange: (newValue: Float) -> Unit,
+    onValueChangeFinished: (() -> Unit)? = null,
     onIconTapped: () -> Unit,
     modifier: Modifier = Modifier,
     sliderColors: PlatformSliderColors,
@@ -85,6 +86,7 @@
         value = value,
         valueRange = state.valueRange,
         onValueChange = onValueChange,
+        onValueChangeFinished = onValueChangeFinished,
         enabled = state.isEnabled,
         icon = {
             state.icon?.let {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/startable/AudioModeLoggerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/startable/AudioModeLoggerStartableTest.kt
new file mode 100644
index 0000000..8bb3672
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/startable/AudioModeLoggerStartableTest.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.volume.domain.startable
+
+import android.media.AudioManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.uiEventLogger
+import com.android.internal.logging.uiEventLoggerFake
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.audioModeInteractor
+import com.android.systemui.volume.audioRepository
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class AudioModeLoggerStartableTest : SysuiTestCase() {
+    @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+    private val kosmos = testKosmos()
+
+    private lateinit var underTest: AudioModeLoggerStartable
+
+    @Before
+    fun setUp() {
+        with(kosmos) {
+            underTest =
+                AudioModeLoggerStartable(
+                    applicationCoroutineScope,
+                    uiEventLogger,
+                    audioModeInteractor
+                )
+        }
+    }
+
+    @Test
+    fun audioMode_inCall() {
+        with(kosmos) {
+            testScope.runTest {
+                audioRepository.setMode(AudioManager.MODE_IN_CALL)
+
+                underTest.start()
+                runCurrent()
+
+                assertThat(uiEventLoggerFake.eventId(0))
+                    .isEqualTo(VolumePanelUiEvent.VOLUME_PANEL_AUDIO_MODE_CHANGE_TO_CALLING.id)
+            }
+        }
+    }
+
+    @Test
+    fun audioMode_notInCall() {
+        with(kosmos) {
+            testScope.runTest {
+                audioRepository.setMode(AudioManager.MODE_NORMAL)
+
+                underTest.start()
+                runCurrent()
+
+                assertThat(uiEventLoggerFake.eventId(0))
+                    .isEqualTo(VolumePanelUiEvent.VOLUME_PANEL_AUDIO_MODE_CHANGE_TO_NORMAL.id)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt
index 2cc1ad3..27a813f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt
@@ -21,6 +21,8 @@
 import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.internal.logging.uiEventLogger
+import com.android.internal.logging.uiEventLoggerFake
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
@@ -29,6 +31,7 @@
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
 import com.android.systemui.volume.panel.volumePanelViewModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -58,7 +61,10 @@
     private lateinit var underTest: BottomBarViewModel
 
     private fun initUnderTest() {
-        underTest = with(kosmos) { BottomBarViewModel(activityStarter, volumePanelViewModel) }
+        underTest =
+            with(kosmos) {
+                BottomBarViewModel(activityStarter, volumePanelViewModel, uiEventLogger)
+            }
     }
 
     @Test
@@ -96,6 +102,8 @@
                         /* userHandle = */ eq(null),
                     )
                 assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_SOUND_SETTINGS)
+                assertThat(uiEventLoggerFake.eventId(0))
+                    .isEqualTo(VolumePanelUiEvent.VOLUME_PANEL_SOUND_SETTINGS_CLICKED.id)
 
                 activityStartedCaptor.value.onActivityStarted(ActivityManager.START_SUCCESS)
                 val volumePanelState by collectLastValue(volumePanelViewModel.volumePanelState)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModelTest.kt
index 610195f..fdeded8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModelTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.internal.logging.uiEventLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
@@ -45,7 +46,12 @@
     fun setup() {
         underTest =
             with(kosmos) {
-                CaptioningViewModel(context, captioningInteractor, testScope.backgroundScope)
+                CaptioningViewModel(
+                    context,
+                    captioningInteractor,
+                    testScope.backgroundScope,
+                    uiEventLogger,
+                )
             }
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt
index ec55c75..da0a229 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt
@@ -46,7 +46,10 @@
 
     @Before
     fun setup() {
-        underTest = MediaOutputAvailabilityCriteria(kosmos.audioModeInteractor)
+        underTest =
+            MediaOutputAvailabilityCriteria(
+                kosmos.audioModeInteractor,
+            )
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
index 462f36d..30524d9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
@@ -22,6 +22,7 @@
 import android.testing.TestableLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.internal.logging.uiEventLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
@@ -64,6 +65,7 @@
                     mediaOutputActionsInteractor,
                     mediaDeviceSessionInteractor,
                     mediaOutputInteractor,
+                    uiEventLogger,
                 )
 
             with(context.orCreateTestableResources) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 5eaccd9..e980794 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -594,7 +594,11 @@
         );
         if (view instanceof FooterView) {
             if (FooterViewRefactor.isEnabled()) {
-                if (((FooterView) view).shouldBeHidden()) {
+                // TODO(b/333445519): shouldBeHidden should reflect whether the shade is closed
+                //  already, so we shouldn't need to use ambientState here. However, currently it
+                //  doesn't get updated quickly enough and can cause the footer to flash when
+                //  closing the shade. As such, we temporarily also check the ambientState directly.
+                if (((FooterView) view).shouldBeHidden() || !ambientState.isShadeExpanded()) {
                     viewState.hidden = true;
                 } else {
                     final float footerEnd = algorithmState.mCurrentExpandedYPosition
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/UiEventLoggerStartableModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/UiEventLoggerStartableModule.kt
new file mode 100644
index 0000000..9b84090
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/UiEventLoggerStartableModule.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.android.systemui.volume.dagger
+
+import com.android.systemui.volume.domain.startable.AudioModeLoggerStartable
+import com.android.systemui.volume.panel.domain.VolumePanelStartable
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+
+@Module
+interface UiEventLoggerStartableModule {
+
+    @Binds
+    @IntoSet
+    fun bindAudioModeLoggerStartable(
+        audioModeLoggerStartable: AudioModeLoggerStartable,
+    ): VolumePanelStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/startable/AudioModeLoggerStartable.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/startable/AudioModeLoggerStartable.kt
new file mode 100644
index 0000000..1244757
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/startable/AudioModeLoggerStartable.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.domain.startable
+
+import com.android.internal.logging.UiEventLogger
+import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.domain.VolumePanelStartable
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.launch
+
+/** Logger for audio mode */
+@VolumePanelScope
+class AudioModeLoggerStartable
+@Inject
+constructor(
+    @VolumePanelScope private val scope: CoroutineScope,
+    private val uiEventLogger: UiEventLogger,
+    private val audioModeInteractor: AudioModeInteractor,
+) : VolumePanelStartable {
+
+    override fun start() {
+        scope.launch {
+            audioModeInteractor.isOngoingCall.distinctUntilChanged().collect { ongoingCall ->
+                uiEventLogger.log(
+                    if (ongoingCall) VolumePanelUiEvent.VOLUME_PANEL_AUDIO_MODE_CHANGE_TO_CALLING
+                    else VolumePanelUiEvent.VOLUME_PANEL_AUDIO_MODE_CHANGE_TO_NORMAL
+                )
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt
index 04d7b1f..3ca9cdf 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt
@@ -18,8 +18,10 @@
 
 import android.content.Intent
 import android.provider.Settings
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
 import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
 import javax.inject.Inject
 
@@ -29,6 +31,7 @@
 constructor(
     private val activityStarter: ActivityStarter,
     private val volumePanelViewModel: VolumePanelViewModel,
+    private val uiEventLogger: UiEventLogger,
 ) {
 
     fun onDoneClicked() {
@@ -36,6 +39,7 @@
     }
 
     fun onSettingsClicked() {
+        uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_SOUND_SETTINGS_CLICKED)
         activityStarter.startActivityDismissingKeyguard(
             /* intent = */ Intent(Settings.ACTION_SOUND_SETTINGS),
             /* onlyProvisioned = */ false,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/domain/CaptioningAvailabilityCriteria.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/domain/CaptioningAvailabilityCriteria.kt
index aab825f..85da1d0 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/domain/CaptioningAvailabilityCriteria.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/domain/CaptioningAvailabilityCriteria.kt
@@ -16,18 +16,36 @@
 
 package com.android.systemui.volume.panel.component.captioning.domain
 
+import com.android.internal.logging.UiEventLogger
 import com.android.settingslib.view.accessibility.domain.interactor.CaptioningInteractor
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
 import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.shareIn
 
 @VolumePanelScope
 class CaptioningAvailabilityCriteria
 @Inject
-constructor(private val captioningInteractor: CaptioningInteractor) :
-    ComponentAvailabilityCriteria {
+constructor(
+    captioningInteractor: CaptioningInteractor,
+    @VolumePanelScope private val scope: CoroutineScope,
+    private val uiEventLogger: UiEventLogger,
+) : ComponentAvailabilityCriteria {
 
-    override fun isAvailable(): Flow<Boolean> =
+    private val availability =
         captioningInteractor.isSystemAudioCaptioningUiEnabled
+            .onEach { visible ->
+                uiEventLogger.log(
+                    if (visible) VolumePanelUiEvent.VOLUME_PANEL_LIVE_CAPTION_TOGGLE_SHOWN
+                    else VolumePanelUiEvent.VOLUME_PANEL_LIVE_CAPTION_TOGGLE_GONE
+                )
+            }
+            .shareIn(scope, SharingStarted.WhileSubscribed(), replay = 1)
+
+    override fun isAvailable(): Flow<Boolean> = availability
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModel.kt
index 92f8f22..01421f8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModel.kt
@@ -17,11 +17,13 @@
 package com.android.systemui.volume.panel.component.captioning.ui.viewmodel
 
 import android.content.Context
+import com.android.internal.logging.UiEventLogger
 import com.android.settingslib.view.accessibility.domain.interactor.CaptioningInteractor
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.res.R
 import com.android.systemui.volume.panel.component.button.ui.viewmodel.ToggleButtonViewModel
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
@@ -38,6 +40,7 @@
     private val context: Context,
     private val captioningInteractor: CaptioningInteractor,
     @VolumePanelScope private val coroutineScope: CoroutineScope,
+    private val uiEventLogger: UiEventLogger,
 ) {
 
     val buttonViewModel: StateFlow<ToggleButtonViewModel?> =
@@ -57,6 +60,13 @@
             .stateIn(coroutineScope, SharingStarted.Eagerly, null)
 
     fun setIsSystemAudioCaptioningEnabled(enabled: Boolean) {
+        uiEventLogger.logWithPosition(
+            VolumePanelUiEvent.VOLUME_PANEL_LIVE_CAPTION_TOGGLE_CLICKED,
+            0,
+            null,
+            if (enabled) VolumePanelUiEvent.LIVE_CAPTION_TOGGLE_ENABLED
+            else VolumePanelUiEvent.LIVE_CAPTION_TOGGLE_DISABLED
+        )
         coroutineScope.launch { captioningInteractor.setIsSystemAudioCaptioningEnabled(enabled) }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index fc9602e..6b237f8e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel
 
 import android.content.Context
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Color
 import com.android.systemui.common.shared.model.Icon
@@ -26,6 +27,7 @@
 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
 import com.android.systemui.volume.panel.component.mediaoutput.shared.model.SessionWithPlayback
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -48,6 +50,7 @@
     private val actionsInteractor: MediaOutputActionsInteractor,
     private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
     interactor: MediaOutputInteractor,
+    private val uiEventLogger: UiEventLogger,
 ) {
 
     private val sessionWithPlayback: StateFlow<SessionWithPlayback?> =
@@ -126,6 +129,7 @@
             )
 
     fun onBarClick(expandable: Expandable) {
+        uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_MEDIA_OUTPUT_CLICKED)
         actionsInteractor.onBarClick(sessionWithPlayback.value, expandable)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
index f022039..4ecdd46 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.volume.panel.component.spatial.ui.viewmodel
 
 import android.content.Context
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.common.shared.model.Color
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.qualifiers.Application
@@ -29,6 +30,7 @@
 import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
 import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
@@ -46,6 +48,7 @@
     @VolumePanelScope private val scope: CoroutineScope,
     availabilityCriteria: SpatialAudioAvailabilityCriteria,
     private val interactor: SpatialAudioComponentInteractor,
+    private val uiEventLogger: UiEventLogger,
 ) {
 
     val spatialAudioButton: StateFlow<ButtonViewModel?> =
@@ -101,6 +104,19 @@
             .stateIn(scope, SharingStarted.Eagerly, emptyList())
 
     fun setEnabled(model: SpatialAudioEnabledModel) {
+        uiEventLogger.logWithPosition(
+            VolumePanelUiEvent.VOLUME_PANEL_SPATIAL_AUDIO_TOGGLE_CLICKED,
+            0,
+            null,
+            when (model) {
+                SpatialAudioEnabledModel.Disabled -> 0
+                SpatialAudioEnabledModel.SpatialAudioEnabled -> 1
+                SpatialAudioEnabledModel.HeadTrackingEnabled -> 2
+                else -> {
+                    -1
+                }
+            }
+        )
         scope.launch { interactor.setEnabled(model) }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index 1ae1ebb..c8cd6fd 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -18,12 +18,14 @@
 
 import android.content.Context
 import android.media.AudioManager
+import com.android.internal.logging.UiEventLogger
 import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
 import com.android.settingslib.volume.shared.model.AudioStream
 import com.android.settingslib.volume.shared.model.AudioStreamModel
 import com.android.settingslib.volume.shared.model.RingerMode
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.res.R
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
@@ -43,6 +45,7 @@
     @Assisted private val coroutineScope: CoroutineScope,
     private val context: Context,
     private val audioVolumeInteractor: AudioVolumeInteractor,
+    private val uiEventLogger: UiEventLogger,
 ) : SliderViewModel {
 
     private val audioStream = audioStreamWrapper.audioStream
@@ -69,6 +72,19 @@
             AudioStream(AudioManager.STREAM_ALARM) to R.string.stream_alarm_unavailable,
             AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_media_unavailable,
         )
+    private val uiEventByStream =
+        mapOf(
+            AudioStream(AudioManager.STREAM_MUSIC) to
+                VolumePanelUiEvent.VOLUME_PANEL_MUSIC_SLIDER_TOUCHED,
+            AudioStream(AudioManager.STREAM_VOICE_CALL) to
+                VolumePanelUiEvent.VOLUME_PANEL_VOICE_CALL_SLIDER_TOUCHED,
+            AudioStream(AudioManager.STREAM_RING) to
+                VolumePanelUiEvent.VOLUME_PANEL_RING_SLIDER_TOUCHED,
+            AudioStream(AudioManager.STREAM_NOTIFICATION) to
+                VolumePanelUiEvent.VOLUME_PANEL_NOTIFICATION_SLIDER_TOUCHED,
+            AudioStream(AudioManager.STREAM_ALARM) to
+                VolumePanelUiEvent.VOLUME_PANEL_ALARM_SLIDER_TOUCHED,
+        )
 
     override val slider: StateFlow<SliderState> =
         combine(
@@ -88,6 +104,10 @@
         }
     }
 
+    override fun onValueChangeFinished() {
+        uiEventByStream[audioStream]?.let { uiEventLogger.log(it) }
+    }
+
     override fun toggleMuted(state: SliderState) {
         val audioViewModel = state as? State
         audioViewModel ?: return
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
index 3689303..956ab66 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
@@ -54,6 +54,8 @@
         }
     }
 
+    override fun onValueChangeFinished() {}
+
     override fun toggleMuted(state: SliderState) {
         // do nothing because this action isn't supported for Cast sliders.
     }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
index 74aee55..7ded8c5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
@@ -25,5 +25,7 @@
 
     fun onValueChanged(state: SliderState, newValue: Float)
 
+    fun onValueChangeFinished()
+
     fun toggleMuted(state: SliderState)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/DefaultMultibindsModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/DefaultMultibindsModule.kt
index d1d5390..f889ed6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/DefaultMultibindsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/DefaultMultibindsModule.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.volume.panel.dagger
 
 import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.domain.VolumePanelStartable
 import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
 import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
 import dagger.Module
@@ -31,4 +32,6 @@
     @Multibinds fun criteriaMap(): Map<VolumePanelComponentKey, ComponentAvailabilityCriteria>
 
     @Multibinds fun components(): Map<VolumePanelComponentKey, VolumePanelUiComponent>
+
+    @Multibinds fun startables(): Set<VolumePanelStartable>
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
index d868c33..ec64f3d9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.volume.panel.dagger
 
+import com.android.systemui.volume.dagger.UiEventLoggerStartableModule
 import com.android.systemui.volume.panel.component.anc.AncModule
 import com.android.systemui.volume.panel.component.bottombar.BottomBarModule
 import com.android.systemui.volume.panel.component.captioning.CaptioningModule
@@ -25,6 +26,7 @@
 import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
 import com.android.systemui.volume.panel.domain.DomainModule
+import com.android.systemui.volume.panel.domain.VolumePanelStartable
 import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
 import com.android.systemui.volume.panel.ui.UiModule
 import com.android.systemui.volume.panel.ui.composable.ComponentsFactory
@@ -47,6 +49,7 @@
             DefaultMultibindsModule::class,
             DomainModule::class,
             UiModule::class,
+            UiEventLoggerStartableModule::class,
             // Components modules
             BottomBarModule::class,
             AncModule::class,
@@ -66,6 +69,8 @@
 
     fun componentsLayoutManager(): ComponentsLayoutManager
 
+    fun volumePanelStartables(): Set<VolumePanelStartable>
+
     @Subcomponent.Factory
     interface Factory : VolumePanelComponentFactory {
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/VolumePanelStartable.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/VolumePanelStartable.kt
new file mode 100644
index 0000000..9c39f5e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/VolumePanelStartable.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.domain
+
+/** Code that needs to be run when Volume Panel is started.. */
+interface VolumePanelStartable {
+    fun start()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/VolumePanelUiEvent.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/VolumePanelUiEvent.kt
new file mode 100644
index 0000000..8b8714f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/VolumePanelUiEvent.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.volume.panel.ui
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+/** UI events for Volume Panel. */
+enum class VolumePanelUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum {
+    @UiEvent(doc = "The volume panel is shown") VOLUME_PANEL_SHOWN(1634),
+    @UiEvent(doc = "The volume panel is gone") VOLUME_PANEL_GONE(1635),
+    @UiEvent(doc = "Media output is clicked") VOLUME_PANEL_MEDIA_OUTPUT_CLICKED(1636),
+    @UiEvent(doc = "Audio mode changed to normal") VOLUME_PANEL_AUDIO_MODE_CHANGE_TO_NORMAL(1680),
+    @UiEvent(doc = "Audio mode changed to calling") VOLUME_PANEL_AUDIO_MODE_CHANGE_TO_CALLING(1681),
+    @UiEvent(doc = "Sound settings is clicked") VOLUME_PANEL_SOUND_SETTINGS_CLICKED(1638),
+    @UiEvent(doc = "The music volume slider is touched") VOLUME_PANEL_MUSIC_SLIDER_TOUCHED(1639),
+    @UiEvent(doc = "The voice call volume slider is touched")
+    VOLUME_PANEL_VOICE_CALL_SLIDER_TOUCHED(1640),
+    @UiEvent(doc = "The ring volume slider is touched") VOLUME_PANEL_RING_SLIDER_TOUCHED(1641),
+    @UiEvent(doc = "The notification volume slider is touched")
+    VOLUME_PANEL_NOTIFICATION_SLIDER_TOUCHED(1642),
+    @UiEvent(doc = "The alarm volume slider is touched") VOLUME_PANEL_ALARM_SLIDER_TOUCHED(1643),
+    @UiEvent(doc = "Live caption toggle is shown") VOLUME_PANEL_LIVE_CAPTION_TOGGLE_SHOWN(1644),
+    @UiEvent(doc = "Live caption toggle is gone") VOLUME_PANEL_LIVE_CAPTION_TOGGLE_GONE(1645),
+    @UiEvent(doc = "Live caption toggle is clicked") VOLUME_PANEL_LIVE_CAPTION_TOGGLE_CLICKED(1646),
+    @UiEvent(doc = "Spatial audio button is shown") VOLUME_PANEL_SPATIAL_AUDIO_BUTTON_SHOWN(1647),
+    @UiEvent(doc = "Spatial audio button is gone") VOLUME_PANEL_SPATIAL_AUDIO_BUTTON_GONE(1648),
+    @UiEvent(doc = "Spatial audio popup is shown") VOLUME_PANEL_SPATIAL_AUDIO_POP_UP_SHOWN(1649),
+    @UiEvent(doc = "Spatial audio toggle is clicked")
+    VOLUME_PANEL_SPATIAL_AUDIO_TOGGLE_CLICKED(1650),
+    @UiEvent(doc = "ANC button is shown") VOLUME_PANEL_ANC_BUTTON_SHOWN(1651),
+    @UiEvent(doc = "ANC button is gone") VOLUME_PANEL_ANC_BUTTON_GONE(1652),
+    @UiEvent(doc = "ANC popup is shown") VOLUME_PANEL_ANC_POPUP_SHOWN(1653),
+    @UiEvent(doc = "ANC toggle is clicked") VOLUME_PANEL_ANC_TOGGLE_CLICKED(1654);
+
+    override fun getId() = metricId
+
+    companion object {
+        const val LIVE_CAPTION_TOGGLE_DISABLED = 0
+        const val LIVE_CAPTION_TOGGLE_ENABLED = 1
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt
index c728fef..ccb91ac 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt
@@ -21,8 +21,10 @@
 import androidx.activity.compose.setContent
 import androidx.activity.enableEdgeToEdge
 import androidx.activity.viewModels
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.volume.panel.shared.flag.VolumePanelFlag
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
 import com.android.systemui.volume.panel.ui.composable.VolumePanelRoot
 import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
 import javax.inject.Inject
@@ -34,6 +36,7 @@
     private val volumePanelViewModelFactory: Provider<VolumePanelViewModel.Factory>,
     private val volumePanelFlag: VolumePanelFlag,
     private val configurationController: ConfigurationController,
+    private val uiEventLogger: UiEventLogger,
 ) : ComponentActivity() {
 
     private val viewModel: VolumePanelViewModel by
@@ -43,8 +46,16 @@
         enableEdgeToEdge()
         super.onCreate(savedInstanceState)
         volumePanelFlag.assertNewVolumePanel()
-
-        setContent { VolumePanelRoot(viewModel = viewModel, onDismiss = ::finish) }
+        uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_SHOWN)
+        setContent {
+            VolumePanelRoot(
+                viewModel = viewModel,
+                onDismiss = {
+                    uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_GONE)
+                    finish()
+                }
+            )
+        }
     }
 
     override fun onContentChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
index 5ae827f..1de4fd1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.statusbar.policy.onConfigChanged
 import com.android.systemui.volume.panel.dagger.VolumePanelComponent
 import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory
+import com.android.systemui.volume.panel.domain.VolumePanelStartable
 import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
 import com.android.systemui.volume.panel.ui.composable.ComponentsFactory
 import com.android.systemui.volume.panel.ui.layout.ComponentsLayout
@@ -109,6 +110,10 @@
                 replay = 1,
             )
 
+    init {
+        volumePanelComponent.volumePanelStartables().onEach(VolumePanelStartable::start)
+    }
+
     fun dismissPanel() {
         mutablePanelVisibility.update { false }
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt
index d341073..348a02e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.volume.panel.dagger.factory.KosmosVolumePanelComponentFactory
 import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
 import com.android.systemui.volume.panel.domain.TestComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.domain.VolumePanelStartable
 import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
 import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractorImpl
 import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
@@ -44,6 +45,8 @@
 var Kosmos.componentsLayoutManager: ComponentsLayoutManager by Kosmos.Fixture()
 var Kosmos.enabledComponents: Collection<VolumePanelComponentKey> by
     Kosmos.Fixture { componentByKey.keys }
+var Kosmos.volumePanelStartables: Set<VolumePanelStartable> by
+    Kosmos.Fixture { emptySet<VolumePanelStartable>() }
 val Kosmos.unavailableCriteria: Provider<ComponentAvailabilityCriteria> by
     Kosmos.Fixture { Provider { TestComponentAvailabilityCriteria(false) } }
 val Kosmos.availableCriteria: Provider<ComponentAvailabilityCriteria> by
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt
index 49041ed..e5f5d4e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt
@@ -22,10 +22,12 @@
 import com.android.systemui.volume.panel.componentsInteractor
 import com.android.systemui.volume.panel.componentsLayoutManager
 import com.android.systemui.volume.panel.dagger.VolumePanelComponent
+import com.android.systemui.volume.panel.domain.VolumePanelStartable
 import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
 import com.android.systemui.volume.panel.ui.composable.ComponentsFactory
 import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
 import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
+import com.android.systemui.volume.panel.volumePanelStartables
 import kotlinx.coroutines.CoroutineScope
 
 class KosmosVolumePanelComponentFactory(private val kosmos: Kosmos) : VolumePanelComponentFactory {
@@ -41,5 +43,8 @@
 
             override fun componentsLayoutManager(): ComponentsLayoutManager =
                 kosmos.componentsLayoutManager
+
+            override fun volumePanelStartables(): Set<VolumePanelStartable> =
+                kosmos.volumePanelStartables
         }
 }
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
new file mode 100644
index 0000000..7890fe0
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
@@ -0,0 +1,464 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.Manifest;
+import android.annotation.BinderThread;
+import android.annotation.EnforcePermission;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.view.MotionEvent;
+import android.view.WindowManager;
+import android.view.inputmethod.CursorAnchorInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ImeTracker;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+import android.window.ImeOnBackInvokedDispatcher;
+
+import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.internal.inputmethod.IBooleanListener;
+import com.android.internal.inputmethod.IConnectionlessHandwritingCallback;
+import com.android.internal.inputmethod.IImeTracker;
+import com.android.internal.inputmethod.IInputMethodClient;
+import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
+import com.android.internal.inputmethod.IRemoteInputConnection;
+import com.android.internal.inputmethod.InputBindResult;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
+import com.android.internal.inputmethod.StartInputFlags;
+import com.android.internal.inputmethod.StartInputReason;
+import com.android.internal.view.IInputMethodManager;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.List;
+
+/**
+ * An actual implementation class of {@link IInputMethodManager.Stub} to allow other classes to
+ * focus on handling IPC callbacks.
+ */
+final class IInputMethodManagerImpl extends IInputMethodManager.Stub {
+
+    /**
+     * Tells that the given permission is already verified before the annotated method gets called.
+     */
+    @Retention(SOURCE)
+    @Target({METHOD})
+    @interface PermissionVerified {
+        String value() default "";
+    }
+
+    @BinderThread
+    interface Callback {
+        void addClient(IInputMethodClient client, IRemoteInputConnection inputConnection,
+                int selfReportedDisplayId);
+
+        InputMethodInfo getCurrentInputMethodInfoAsUser(@UserIdInt int userId);
+
+        List<InputMethodInfo> getInputMethodList(@UserIdInt int userId,
+                @DirectBootAwareness int directBootAwareness);
+
+        List<InputMethodInfo> getEnabledInputMethodList(@UserIdInt int userId);
+
+        List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
+                boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId);
+
+        InputMethodSubtype getLastInputMethodSubtype(@UserIdInt int userId);
+
+        boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
+                @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
+                @MotionEvent.ToolType int lastClickToolType, ResultReceiver resultReceiver,
+                @SoftInputShowHideReason int reason);
+
+        boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
+                @Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
+                ResultReceiver resultReceiver, @SoftInputShowHideReason int reason);
+
+        @PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
+        void hideSoftInputFromServerForTest();
+
+        void startInputOrWindowGainedFocusAsync(
+                @StartInputReason int startInputReason, IInputMethodClient client,
+                IBinder windowToken, @StartInputFlags int startInputFlags,
+                @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags,
+                @Nullable EditorInfo editorInfo, IRemoteInputConnection inputConnection,
+                IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+                int unverifiedTargetSdkVersion, @UserIdInt int userId,
+                @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq);
+
+        InputBindResult startInputOrWindowGainedFocus(
+                @StartInputReason int startInputReason, IInputMethodClient client,
+                IBinder windowToken, @StartInputFlags int startInputFlags,
+                @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags,
+                @Nullable EditorInfo editorInfo, IRemoteInputConnection inputConnection,
+                IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+                int unverifiedTargetSdkVersion, @UserIdInt int userId,
+                @NonNull ImeOnBackInvokedDispatcher imeDispatcher);
+
+        void showInputMethodPickerFromClient(IInputMethodClient client, int auxiliarySubtypeMode);
+
+        @PermissionVerified(Manifest.permission.WRITE_SECURE_SETTINGS)
+        void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId);
+
+        @PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
+        boolean isInputMethodPickerShownForTest();
+
+        InputMethodSubtype getCurrentInputMethodSubtype(@UserIdInt int userId);
+
+        void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes,
+                @UserIdInt int userId);
+
+        void setExplicitlyEnabledInputMethodSubtypes(String imeId,
+                @NonNull int[] subtypeHashCodes, @UserIdInt int userId);
+
+        int getInputMethodWindowVisibleHeight(IInputMethodClient client);
+
+        void reportPerceptibleAsync(IBinder windowToken, boolean perceptible);
+
+        @PermissionVerified(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+        void removeImeSurface();
+
+        void removeImeSurfaceFromWindowAsync(IBinder windowToken);
+
+        void startProtoDump(byte[] bytes, int i, String s);
+
+        boolean isImeTraceEnabled();
+
+        @PermissionVerified(Manifest.permission.CONTROL_UI_TRACING)
+        void startImeTrace();
+
+        @PermissionVerified(Manifest.permission.CONTROL_UI_TRACING)
+        void stopImeTrace();
+
+        void startStylusHandwriting(IInputMethodClient client);
+
+        void startConnectionlessStylusHandwriting(IInputMethodClient client, @UserIdInt int userId,
+                @Nullable CursorAnchorInfo cursorAnchorInfo, @Nullable String delegatePackageName,
+                @Nullable String delegatorPackageName,
+                @NonNull IConnectionlessHandwritingCallback callback);
+
+        boolean acceptStylusHandwritingDelegation(@NonNull IInputMethodClient client,
+                @UserIdInt int userId, @NonNull String delegatePackageName,
+                @NonNull String delegatorPackageName,
+                @InputMethodManager.HandwritingDelegateFlags int flags);
+
+        void acceptStylusHandwritingDelegationAsync(@NonNull IInputMethodClient client,
+                @UserIdInt int userId, @NonNull String delegatePackageName,
+                @NonNull String delegatorPackageName,
+                @InputMethodManager.HandwritingDelegateFlags int flags, IBooleanListener callback);
+
+        void prepareStylusHandwritingDelegation(@NonNull IInputMethodClient client,
+                @UserIdInt int userId, @NonNull String delegatePackageName,
+                @NonNull String delegatorPackageName);
+
+        boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId, boolean connectionless);
+
+        @PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
+        void addVirtualStylusIdForTestSession(IInputMethodClient client);
+
+        @PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
+        void setStylusWindowIdleTimeoutForTest(IInputMethodClient client, long timeout);
+
+        IImeTracker getImeTrackerService();
+
+        void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+                @Nullable FileDescriptor err, @NonNull String[] args,
+                @Nullable ShellCallback callback, @NonNull ResultReceiver resultReceiver,
+                @NonNull Binder self);
+
+        void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout, @Nullable String[] args);
+    }
+
+    @NonNull
+    private final Callback mCallback;
+
+    private IInputMethodManagerImpl(@NonNull Callback callback) {
+        mCallback = callback;
+    }
+
+    static IInputMethodManagerImpl create(@NonNull Callback callback) {
+        return new IInputMethodManagerImpl(callback);
+    }
+
+    @Override
+    public void addClient(IInputMethodClient client, IRemoteInputConnection inputmethod,
+            int untrustedDisplayId) {
+        mCallback.addClient(client, inputmethod, untrustedDisplayId);
+    }
+
+    @Override
+    public InputMethodInfo getCurrentInputMethodInfoAsUser(@UserIdInt int userId) {
+        return mCallback.getCurrentInputMethodInfoAsUser(userId);
+    }
+
+    @Override
+    public List<InputMethodInfo> getInputMethodList(@UserIdInt int userId,
+            int directBootAwareness) {
+        return mCallback.getInputMethodList(userId, directBootAwareness);
+    }
+
+    @Override
+    public List<InputMethodInfo> getEnabledInputMethodList(@UserIdInt int userId) {
+        return mCallback.getEnabledInputMethodList(userId);
+    }
+
+    @Override
+    public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
+            boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) {
+        return mCallback.getEnabledInputMethodSubtypeList(imiId, allowsImplicitlyEnabledSubtypes,
+                userId);
+    }
+
+    @Override
+    public InputMethodSubtype getLastInputMethodSubtype(@UserIdInt int userId) {
+        return mCallback.getLastInputMethodSubtype(userId);
+    }
+
+    @Override
+    public boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
+            @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
+            @MotionEvent.ToolType int lastClickToolType, ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {
+        return mCallback.showSoftInput(client, windowToken, statsToken, flags, lastClickToolType,
+                resultReceiver, reason);
+    }
+
+    @Override
+    public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
+            @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
+            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+        return mCallback.hideSoftInput(client, windowToken, statsToken, flags, resultReceiver,
+                reason);
+    }
+
+    @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+    @Override
+    public void hideSoftInputFromServerForTest() {
+        super.hideSoftInputFromServerForTest_enforcePermission();
+
+        mCallback.hideSoftInputFromServerForTest();
+    }
+
+    @Override
+    public InputBindResult startInputOrWindowGainedFocus(
+            @StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken,
+            @StartInputFlags int startInputFlags,
+            @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode,
+            int windowFlags, @Nullable EditorInfo editorInfo,
+            IRemoteInputConnection inputConnection,
+            IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+            int unverifiedTargetSdkVersion, @UserIdInt int userId,
+            @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+        return mCallback.startInputOrWindowGainedFocus(
+                startInputReason, client, windowToken, startInputFlags, softInputMode,
+                windowFlags, editorInfo, inputConnection, remoteAccessibilityInputConnection,
+                unverifiedTargetSdkVersion, userId, imeDispatcher);
+    }
+
+    @Override
+    public void startInputOrWindowGainedFocusAsync(@StartInputReason int startInputReason,
+            IInputMethodClient client, IBinder windowToken,
+            @StartInputFlags int startInputFlags,
+            @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode,
+            int windowFlags, @Nullable EditorInfo editorInfo,
+            IRemoteInputConnection inputConnection,
+            IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+            int unverifiedTargetSdkVersion, @UserIdInt int userId,
+            @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq) {
+        mCallback.startInputOrWindowGainedFocusAsync(
+                startInputReason, client, windowToken, startInputFlags, softInputMode,
+                windowFlags, editorInfo, inputConnection, remoteAccessibilityInputConnection,
+                unverifiedTargetSdkVersion, userId, imeDispatcher, startInputSeq);
+    }
+
+    @Override
+    public void showInputMethodPickerFromClient(IInputMethodClient client,
+            int auxiliarySubtypeMode) {
+        mCallback.showInputMethodPickerFromClient(client, auxiliarySubtypeMode);
+    }
+
+    @EnforcePermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+    @Override
+    public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
+        super.showInputMethodPickerFromSystem_enforcePermission();
+
+        mCallback.showInputMethodPickerFromSystem(auxiliarySubtypeMode, displayId);
+
+    }
+
+    @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+    @Override
+    public boolean isInputMethodPickerShownForTest() {
+        super.isInputMethodPickerShownForTest_enforcePermission();
+
+        return mCallback.isInputMethodPickerShownForTest();
+    }
+
+    @Override
+    public InputMethodSubtype getCurrentInputMethodSubtype(@UserIdInt int userId) {
+        return mCallback.getCurrentInputMethodSubtype(userId);
+    }
+
+    @Override
+    public void setAdditionalInputMethodSubtypes(String id, InputMethodSubtype[] subtypes,
+            @UserIdInt int userId) {
+        mCallback.setAdditionalInputMethodSubtypes(id, subtypes, userId);
+    }
+
+    @Override
+    public void setExplicitlyEnabledInputMethodSubtypes(String imeId, int[] subtypeHashCodes,
+            @UserIdInt int userId) {
+        mCallback.setExplicitlyEnabledInputMethodSubtypes(imeId, subtypeHashCodes, userId);
+    }
+
+    @Override
+    public int getInputMethodWindowVisibleHeight(IInputMethodClient client) {
+        return mCallback.getInputMethodWindowVisibleHeight(client);
+    }
+
+    @Override
+    public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible) {
+        mCallback.reportPerceptibleAsync(windowToken, perceptible);
+    }
+
+    @EnforcePermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+    @Override
+    public void removeImeSurface() {
+        super.removeImeSurface_enforcePermission();
+
+        mCallback.removeImeSurface();
+    }
+
+    @Override
+    public void removeImeSurfaceFromWindowAsync(IBinder windowToken) {
+        mCallback.removeImeSurfaceFromWindowAsync(windowToken);
+    }
+
+    @Override
+    public void startProtoDump(byte[] protoDump, int source, String where) {
+        mCallback.startProtoDump(protoDump, source, where);
+    }
+
+    @Override
+    public boolean isImeTraceEnabled() {
+        return mCallback.isImeTraceEnabled();
+    }
+
+    @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
+    @Override
+    public void startImeTrace() {
+        super.startImeTrace_enforcePermission();
+
+        mCallback.startImeTrace();
+    }
+
+    @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
+    @Override
+    public void stopImeTrace() {
+        super.stopImeTrace_enforcePermission();
+
+        mCallback.stopImeTrace();
+    }
+
+    @Override
+    public void startStylusHandwriting(IInputMethodClient client) {
+        mCallback.startStylusHandwriting(client);
+    }
+
+    @Override
+    public void startConnectionlessStylusHandwriting(IInputMethodClient client,
+            @UserIdInt int userId, CursorAnchorInfo cursorAnchorInfo,
+            String delegatePackageName, String delegatorPackageName,
+            IConnectionlessHandwritingCallback callback) {
+        mCallback.startConnectionlessStylusHandwriting(client, userId, cursorAnchorInfo,
+                delegatePackageName, delegatorPackageName, callback);
+    }
+
+    @Override
+    public void prepareStylusHandwritingDelegation(IInputMethodClient client, @UserIdInt int userId,
+            String delegatePackageName, String delegatorPackageName) {
+        mCallback.prepareStylusHandwritingDelegation(client, userId,
+                delegatePackageName, delegatorPackageName);
+    }
+
+    @Override
+    public boolean acceptStylusHandwritingDelegation(IInputMethodClient client,
+            @UserIdInt int userId, String delegatePackageName, String delegatorPackageName,
+            @InputMethodManager.HandwritingDelegateFlags int flags) {
+        return mCallback.acceptStylusHandwritingDelegation(client, userId,
+                delegatePackageName, delegatorPackageName, flags);
+    }
+
+    @Override
+    public void acceptStylusHandwritingDelegationAsync(IInputMethodClient client,
+            @UserIdInt int userId, String delegatePackageName, String delegatorPackageName,
+            @InputMethodManager.HandwritingDelegateFlags int flags,
+            IBooleanListener callback) {
+        mCallback.acceptStylusHandwritingDelegationAsync(client, userId,
+                delegatePackageName, delegatorPackageName, flags, callback);
+    }
+
+    @Override
+    public boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId,
+            boolean connectionless) {
+        return mCallback.isStylusHandwritingAvailableAsUser(userId, connectionless);
+    }
+
+    @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+    @Override
+    public void addVirtualStylusIdForTestSession(IInputMethodClient client) {
+        super.addVirtualStylusIdForTestSession_enforcePermission();
+
+        mCallback.addVirtualStylusIdForTestSession(client);
+    }
+
+    @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+    @Override
+    public void setStylusWindowIdleTimeoutForTest(IInputMethodClient client, long timeout) {
+        super.setStylusWindowIdleTimeoutForTest_enforcePermission();
+
+        mCallback.setStylusWindowIdleTimeoutForTest(client, timeout);
+    }
+
+    @Override
+    public IImeTracker getImeTrackerService() {
+        return mCallback.getImeTrackerService();
+    }
+
+    @Override
+    public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+            @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback,
+            @NonNull ResultReceiver resultReceiver) {
+        mCallback.onShellCommand(in, out, err, args, callback, resultReceiver, this);
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        mCallback.dump(fd, pw, args);
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 1d07eee..03a85c4 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -61,7 +61,6 @@
 import android.annotation.BinderThread;
 import android.annotation.DrawableRes;
 import android.annotation.DurationMillisLong;
-import android.annotation.EnforcePermission;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -172,7 +171,6 @@
 import com.android.internal.util.ConcurrentUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
-import com.android.internal.view.IInputMethodManager;
 import com.android.server.AccessibilityManagerInternal;
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
@@ -211,8 +209,9 @@
 /**
  * This class provides a system service that manages input methods.
  */
-public final class InputMethodManagerService extends IInputMethodManager.Stub
-        implements Handler.Callback {
+public final class InputMethodManagerService implements IInputMethodManagerImpl.Callback,
+        ZeroJankProxy.Callback, Handler.Callback {
+
     // Virtual device id for test.
     private static final Integer VIRTUAL_STYLUS_ID_FOR_TEST = 999999;
     static final boolean DEBUG = false;
@@ -1236,21 +1235,14 @@
         @Override
         public void onStart() {
             mService.publishLocalService();
-            IInputMethodManager.Stub service;
+            IInputMethodManagerImpl.Callback service;
             if (Flags.useZeroJankProxy()) {
-                service =
-                        new ZeroJankProxy(
-                                mService.mHandler::post,
-                                mService,
-                                () -> {
-                                    synchronized (ImfLock.class) {
-                                        return mService.isInputShown();
-                                    }
-                                });
+                service = new ZeroJankProxy(mService.mHandler::post, mService);
             } else {
                 service = mService;
             }
-            publishBinderService(Context.INPUT_METHOD_SERVICE, service, false /*allowIsolated*/,
+            publishBinderService(Context.INPUT_METHOD_SERVICE,
+                    IInputMethodManagerImpl.create(service), false /*allowIsolated*/,
                     DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
         }
 
@@ -1935,10 +1927,10 @@
     }
 
     @Nullable
-    ClientState getClientState(IInputMethodClient client) {
-        synchronized (ImfLock.class) {
-            return mClientController.getClient(client.asBinder());
-        }
+    @GuardedBy("ImfLock.class")
+    @Override
+    public ClientState getClientStateLocked(IInputMethodClient client) {
+        return mClientController.getClient(client.asBinder());
     }
 
     // TODO(b/314150112): Move this to ClientController.
@@ -2017,7 +2009,8 @@
     }
 
     @GuardedBy("ImfLock.class")
-    private boolean isInputShown() {
+    @Override
+    public boolean isInputShownLocked() {
         return mVisibilityStateComputer.isInputShown();
     }
 
@@ -3133,11 +3126,16 @@
     public void startConnectionlessStylusHandwriting(IInputMethodClient client, int userId,
             @Nullable CursorAnchorInfo cursorAnchorInfo, @Nullable String delegatePackageName,
             @Nullable String delegatorPackageName,
-            @NonNull IConnectionlessHandwritingCallback callback) throws RemoteException {
+            @NonNull IConnectionlessHandwritingCallback callback) {
         synchronized (ImfLock.class) {
             if (!mBindingController.supportsConnectionlessStylusHandwriting()) {
                 Slog.w(TAG, "Connectionless stylus handwriting mode unsupported by IME.");
-                callback.onError(CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED);
+                try {
+                    callback.onError(CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Failed to report CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED", e);
+                    e.rethrowAsRuntimeException();
+                }
                 return;
             }
         }
@@ -3148,7 +3146,12 @@
             synchronized (ImfLock.class) {
                 if (!mClientController.verifyClientAndPackageMatch(client, delegatorPackageName)) {
                     Slog.w(TAG, "startConnectionlessStylusHandwriting() fail");
-                    callback.onError(CONNECTIONLESS_HANDWRITING_ERROR_OTHER);
+                    try {
+                        callback.onError(CONNECTIONLESS_HANDWRITING_ERROR_OTHER);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Failed to report CONNECTIONLESS_HANDWRITING_ERROR_OTHER", e);
+                        e.rethrowAsRuntimeException();
+                    }
                     throw new IllegalArgumentException("Delegator doesn't match UID");
                 }
             }
@@ -3172,7 +3175,12 @@
 
         if (!startStylusHandwriting(
                 client, false, immsCallback, cursorAnchorInfo, isForDelegation)) {
-            callback.onError(CONNECTIONLESS_HANDWRITING_ERROR_OTHER);
+            try {
+                callback.onError(CONNECTIONLESS_HANDWRITING_ERROR_OTHER);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to report CONNECTIONLESS_HANDWRITING_ERROR_OTHER", e);
+                e.rethrowAsRuntimeException();
+            }
         }
     }
 
@@ -3272,11 +3280,15 @@
             @UserIdInt int userId,
             @NonNull String delegatePackageName,
             @NonNull String delegatorPackageName,
-            @InputMethodManager.HandwritingDelegateFlags int flags, IBooleanListener callback)
-            throws RemoteException {
+            @InputMethodManager.HandwritingDelegateFlags int flags, IBooleanListener callback) {
         boolean result = acceptStylusHandwritingDelegation(
                 client, userId, delegatePackageName, delegatorPackageName, flags);
-        callback.onResult(result);
+        try {
+            callback.onResult(result);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to report result=" + result, e);
+            e.rethrowAsRuntimeException();
+        }
     }
 
     @Override
@@ -3367,7 +3379,7 @@
     @GuardedBy("ImfLock.class")
     boolean showCurrentInputLocked(IBinder windowToken,
             @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
-            int lastClickToolType, @Nullable ResultReceiver resultReceiver,
+            @MotionEvent.ToolType int lastClickToolType, @Nullable ResultReceiver resultReceiver,
             @SoftInputShowHideReason int reason) {
         if (!mVisibilityStateComputer.onImeShowFlags(statsToken, flags)) {
             return false;
@@ -3413,7 +3425,7 @@
                 "InputMethodManagerService#hideSoftInput");
         synchronized (ImfLock.class) {
             if (!canInteractWithImeLocked(uid, client, "hideSoftInput", statsToken)) {
-                if (isInputShown()) {
+                if (isInputShownLocked()) {
                     ImeTracker.forLogging().onFailed(
                             statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
                 } else {
@@ -3436,10 +3448,8 @@
     }
 
     @Override
-    @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+    @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
     public void hideSoftInputFromServerForTest() {
-        super.hideSoftInputFromServerForTest_enforcePermission();
-
         synchronized (ImfLock.class) {
             hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
                     SoftInputShowHideReason.HIDE_SOFT_INPUT);
@@ -3472,7 +3482,7 @@
         // TODO(b/246309664): Clean up IMMS#mImeWindowVis
         IInputMethodInvoker curMethod = getCurMethodLocked();
         final boolean shouldHideSoftInput = curMethod != null
-                && (isInputShown() || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
+                && (isInputShownLocked() || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
 
         mVisibilityStateComputer.requestImeVisibility(windowToken, false);
         if (shouldHideSoftInput) {
@@ -3845,13 +3855,11 @@
         }
     }
 
-    @EnforcePermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+    @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.WRITE_SECURE_SETTINGS)
     @Override
     public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
         // Always call subtype picker, because subtype picker is a superset of input method
         // picker.
-        super.showInputMethodPickerFromSystem_enforcePermission();
-
         mHandler.obtainMessage(MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode, displayId)
                 .sendToTarget();
     }
@@ -3859,10 +3867,8 @@
     /**
      * A test API for CTS to make sure that the input method menu is showing.
      */
-    @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+    @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
     public boolean isInputMethodPickerShownForTest() {
-        super.isInputMethodPickerShownForTest_enforcePermission();
-
         synchronized (ImfLock.class) {
             return mMenuController.isisInputMethodPickerShownForTestLocked();
         }
@@ -4162,11 +4168,9 @@
         });
     }
 
-    @EnforcePermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+    @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
     @Override
     public void removeImeSurface() {
-        super.removeImeSurface_enforcePermission();
-
         mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget();
     }
 
@@ -4275,11 +4279,9 @@
      * a stylus deviceId is not already registered on device.
      */
     @BinderThread
-    @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+    @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
     @Override
     public void addVirtualStylusIdForTestSession(IInputMethodClient client) {
-        super.addVirtualStylusIdForTestSession_enforcePermission();
-
         int uid = Binder.getCallingUid();
         synchronized (ImfLock.class) {
             if (!canInteractWithImeLocked(uid, client, "addVirtualStylusIdForTestSession",
@@ -4302,12 +4304,10 @@
      * @param timeout to set in milliseconds. To reset to default, use a value <= zero.
      */
     @BinderThread
-    @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+    @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
     @Override
     public void setStylusWindowIdleTimeoutForTest(
             IInputMethodClient client, @DurationMillisLong long timeout) {
-        super.setStylusWindowIdleTimeoutForTest_enforcePermission();
-
         int uid = Binder.getCallingUid();
         synchronized (ImfLock.class) {
             if (!canInteractWithImeLocked(uid, client, "setStylusWindowIdleTimeoutForTest",
@@ -4403,10 +4403,9 @@
     }
 
     @BinderThread
-    @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
+    @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.CONTROL_UI_TRACING)
     @Override
     public void startImeTrace() {
-        super.startImeTrace_enforcePermission();
         ImeTracing.getInstance().startTrace(null /* printwriter */);
         synchronized (ImfLock.class) {
             mClientController.forAllClients(c -> c.mClient.setImeTraceEnabled(true /* enabled */));
@@ -4414,11 +4413,9 @@
     }
 
     @BinderThread
-    @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
+    @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.CONTROL_UI_TRACING)
     @Override
     public void stopImeTrace() {
-        super.stopImeTrace_enforcePermission();
-
         ImeTracing.getInstance().stopTrace(null /* printwriter */);
         synchronized (ImfLock.class) {
             mClientController.forAllClients(c -> c.mClient.setImeTraceEnabled(false /* enabled */));
@@ -4698,7 +4695,7 @@
                         // implemented so that auxiliary subtypes will be excluded when the soft
                         // keyboard is invisible.
                         synchronized (ImfLock.class) {
-                            showAuxSubtypes = isInputShown();
+                            showAuxSubtypes = isInputShownLocked();
                         }
                         break;
                     case InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES:
@@ -5845,7 +5842,7 @@
 
     @BinderThread
     @Override
-    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
 
         PriorityDump.dump(mPriorityDumper, fd, pw, args);
@@ -5975,7 +5972,7 @@
     public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
             @Nullable FileDescriptor err,
             @NonNull String[] args, @Nullable ShellCallback callback,
-            @NonNull ResultReceiver resultReceiver) throws RemoteException {
+            @NonNull ResultReceiver resultReceiver, @NonNull Binder self) {
         final int callingUid = Binder.getCallingUid();
         // Reject any incoming calls from non-shell users, including ones from the system user.
         if (callingUid != Process.ROOT_UID && callingUid != Process.SHELL_UID) {
@@ -5996,7 +5993,7 @@
             throw new SecurityException(errorMsg);
         }
         new ShellCommandImpl(this).exec(
-                this, in, out, err, args, callback, resultReceiver);
+                self, in, out, err, args, callback, resultReceiver);
     }
 
     private static final class ShellCommandImpl extends ShellCommand {
diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
index ffc2319..1cd1ddc 100644
--- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
+++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
@@ -36,17 +36,16 @@
 
 import android.Manifest;
 import android.annotation.BinderThread;
-import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
 import android.os.Binder;
 import android.os.IBinder;
-import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.util.Slog;
+import android.view.MotionEvent;
 import android.view.WindowManager;
 import android.view.inputmethod.CursorAnchorInfo;
 import android.view.inputmethod.EditorInfo;
@@ -56,6 +55,7 @@
 import android.view.inputmethod.InputMethodSubtype;
 import android.window.ImeOnBackInvokedDispatcher;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.inputmethod.DirectBootAwareness;
 import com.android.internal.inputmethod.IBooleanListener;
 import com.android.internal.inputmethod.IConnectionlessHandwritingCallback;
@@ -76,22 +76,25 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
-import java.util.function.BooleanSupplier;
 
 /**
  * A proxy that processes all {@link IInputMethodManager} calls asynchronously.
- * @hide
  */
-public class ZeroJankProxy extends IInputMethodManager.Stub {
+final class ZeroJankProxy implements IInputMethodManagerImpl.Callback {
 
-    private final IInputMethodManager mInner;
+    interface Callback extends IInputMethodManagerImpl.Callback {
+        @GuardedBy("ImfLock.class")
+        ClientState getClientStateLocked(IInputMethodClient client);
+        @GuardedBy("ImfLock.class")
+        boolean isInputShownLocked();
+    }
+
+    private final Callback mInner;
     private final Executor mExecutor;
-    private final BooleanSupplier mIsInputShown;
 
-    ZeroJankProxy(Executor executor, IInputMethodManager inner, BooleanSupplier isInputShown) {
+    ZeroJankProxy(Executor executor, Callback inner) {
         mInner = inner;
         mExecutor = executor;
-        mIsInputShown = isInputShown;
     }
 
     private void offload(ThrowingRunnable r) {
@@ -126,45 +129,43 @@
 
     @Override
     public void addClient(IInputMethodClient client, IRemoteInputConnection inputConnection,
-            int selfReportedDisplayId) throws RemoteException {
+            int selfReportedDisplayId) {
         offload(() -> mInner.addClient(client, inputConnection, selfReportedDisplayId));
     }
 
     @Override
-    public InputMethodInfo getCurrentInputMethodInfoAsUser(int userId) throws RemoteException {
+    public InputMethodInfo getCurrentInputMethodInfoAsUser(int userId) {
         return mInner.getCurrentInputMethodInfoAsUser(userId);
     }
 
     @Override
     public List<InputMethodInfo> getInputMethodList(
-            int userId, @DirectBootAwareness int directBootAwareness) throws RemoteException {
+            int userId, @DirectBootAwareness int directBootAwareness) {
         return mInner.getInputMethodList(userId, directBootAwareness);
     }
 
     @Override
-    public List<InputMethodInfo> getEnabledInputMethodList(int userId) throws RemoteException {
+    public List<InputMethodInfo> getEnabledInputMethodList(int userId) {
         return mInner.getEnabledInputMethodList(userId);
     }
 
     @Override
     public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
-            boolean allowsImplicitlyEnabledSubtypes, int userId)
-            throws RemoteException {
+            boolean allowsImplicitlyEnabledSubtypes, int userId) {
         return mInner.getEnabledInputMethodSubtypeList(imiId, allowsImplicitlyEnabledSubtypes,
                 userId);
     }
 
     @Override
-    public InputMethodSubtype getLastInputMethodSubtype(int userId) throws RemoteException {
+    public InputMethodSubtype getLastInputMethodSubtype(int userId) {
         return mInner.getLastInputMethodSubtype(userId);
     }
 
     @Override
     public boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
             @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
-            int lastClickTooType, ResultReceiver resultReceiver,
-            @SoftInputShowHideReason int reason)
-            throws RemoteException {
+            @MotionEvent.ToolType int lastClickToolType, ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {
         offload(
                 () -> {
                     if (!mInner.showSoftInput(
@@ -172,7 +173,7 @@
                             windowToken,
                             statsToken,
                             flags,
-                            lastClickTooType,
+                            lastClickToolType,
                             resultReceiver,
                             reason)) {
                         sendResultReceiverFailure(resultReceiver);
@@ -184,8 +185,7 @@
     @Override
     public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
             @Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
-            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason)
-            throws RemoteException {
+            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         offload(
                 () -> {
                     if (!mInner.hideSoftInput(
@@ -200,17 +200,19 @@
         if (resultReceiver == null) {
             return;
         }
-        resultReceiver.send(
-                mIsInputShown.getAsBoolean()
+        final boolean isInputShown;
+        synchronized (ImfLock.class) {
+            isInputShown = mInner.isInputShownLocked();
+        }
+        resultReceiver.send(isInputShown
                         ? InputMethodManager.RESULT_UNCHANGED_SHOWN
                         : InputMethodManager.RESULT_UNCHANGED_HIDDEN,
                 null);
     }
 
     @Override
-    @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
-    public void hideSoftInputFromServerForTest() throws RemoteException {
-        super.hideSoftInputFromServerForTest_enforcePermission();
+    @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
+    public void hideSoftInputFromServerForTest() {
         mInner.hideSoftInputFromServerForTest();
     }
 
@@ -225,8 +227,7 @@
             IRemoteInputConnection inputConnection,
             IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
             int unverifiedTargetSdkVersion, @UserIdInt int userId,
-            @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq)
-            throws RemoteException {
+            @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq) {
         offload(() -> {
             InputBindResult result = mInner.startInputOrWindowGainedFocus(startInputReason, client,
                     windowToken, startInputFlags, softInputMode, windowFlags,
@@ -249,99 +250,92 @@
             IRemoteInputConnection inputConnection,
             IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
             int unverifiedTargetSdkVersion, @UserIdInt int userId,
-            @NonNull ImeOnBackInvokedDispatcher imeDispatcher)
-            throws RemoteException {
+            @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
         // Should never be called when flag is enabled i.e. when this proxy is used.
         return null;
     }
 
     @Override
     public void showInputMethodPickerFromClient(IInputMethodClient client,
-            int auxiliarySubtypeMode)
-            throws RemoteException {
+            int auxiliarySubtypeMode) {
         offload(() -> mInner.showInputMethodPickerFromClient(client, auxiliarySubtypeMode));
     }
 
-    @EnforcePermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+    @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.WRITE_SECURE_SETTINGS)
     @Override
-    public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId)
-            throws RemoteException {
+    public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
         mInner.showInputMethodPickerFromSystem(auxiliarySubtypeMode, displayId);
     }
 
-    @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+    @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
     @Override
-    public boolean isInputMethodPickerShownForTest() throws RemoteException {
-        super.isInputMethodPickerShownForTest_enforcePermission();
+    public boolean isInputMethodPickerShownForTest() {
         return mInner.isInputMethodPickerShownForTest();
     }
 
     @Override
-    public InputMethodSubtype getCurrentInputMethodSubtype(int userId) throws RemoteException {
+    public InputMethodSubtype getCurrentInputMethodSubtype(int userId) {
         return mInner.getCurrentInputMethodSubtype(userId);
     }
 
     @Override
     public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes,
-            @UserIdInt int userId) throws RemoteException {
+            @UserIdInt int userId) {
         mInner.setAdditionalInputMethodSubtypes(imiId, subtypes, userId);
     }
 
     @Override
     public void setExplicitlyEnabledInputMethodSubtypes(String imeId,
-            @NonNull int[] subtypeHashCodes, @UserIdInt int userId) throws RemoteException {
+            @NonNull int[] subtypeHashCodes, @UserIdInt int userId) {
         mInner.setExplicitlyEnabledInputMethodSubtypes(imeId, subtypeHashCodes, userId);
     }
 
     @Override
-    public int getInputMethodWindowVisibleHeight(IInputMethodClient client)
-            throws RemoteException {
+    public int getInputMethodWindowVisibleHeight(IInputMethodClient client) {
         return mInner.getInputMethodWindowVisibleHeight(client);
     }
 
     @Override
-    public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible)
-            throws RemoteException {
+    public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible) {
         // Already async TODO(b/293640003): ordering issues?
         mInner.reportPerceptibleAsync(windowToken, perceptible);
     }
 
-    @EnforcePermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+    @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
     @Override
-    public void removeImeSurface() throws RemoteException {
+    public void removeImeSurface() {
         mInner.removeImeSurface();
     }
 
     @Override
-    public void removeImeSurfaceFromWindowAsync(IBinder windowToken) throws RemoteException {
+    public void removeImeSurfaceFromWindowAsync(IBinder windowToken) {
         mInner.removeImeSurfaceFromWindowAsync(windowToken);
     }
 
     @Override
-    public void startProtoDump(byte[] bytes, int i, String s) throws RemoteException {
+    public void startProtoDump(byte[] bytes, int i, String s) {
         mInner.startProtoDump(bytes, i, s);
     }
 
     @Override
-    public boolean isImeTraceEnabled() throws RemoteException {
+    public boolean isImeTraceEnabled() {
         return mInner.isImeTraceEnabled();
     }
 
-    @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
+    @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.CONTROL_UI_TRACING)
     @Override
-    public void startImeTrace() throws RemoteException {
+    public void startImeTrace() {
         mInner.startImeTrace();
     }
 
-    @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
+    @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.CONTROL_UI_TRACING)
     @Override
-    public void stopImeTrace() throws RemoteException {
+    public void stopImeTrace() {
         mInner.stopImeTrace();
     }
 
     @Override
-    public void startStylusHandwriting(IInputMethodClient client)
-            throws RemoteException {
+    public void startStylusHandwriting(IInputMethodClient client) {
         offload(() -> mInner.startStylusHandwriting(client));
     }
 
@@ -349,7 +343,7 @@
     public void startConnectionlessStylusHandwriting(IInputMethodClient client, int userId,
             @Nullable CursorAnchorInfo cursorAnchorInfo, @Nullable String delegatePackageName,
             @Nullable String delegatorPackageName,
-            @NonNull IConnectionlessHandwritingCallback callback) throws RemoteException {
+            @NonNull IConnectionlessHandwritingCallback callback) {
         offload(() -> mInner.startConnectionlessStylusHandwriting(
                 client, userId, cursorAnchorInfo, delegatePackageName, delegatorPackageName,
                 callback));
@@ -363,14 +357,11 @@
             @NonNull String delegatorPackageName,
             @InputMethodManager.HandwritingDelegateFlags int flags) {
         try {
-            return CompletableFuture.supplyAsync(() -> {
-                try {
-                    return mInner.acceptStylusHandwritingDelegation(
-                            client, userId, delegatePackageName, delegatorPackageName, flags);
-                } catch (RemoteException e) {
-                    throw new RuntimeException(e);
-                }
-            }, this::offload).get();
+            return CompletableFuture.supplyAsync(() ->
+                            mInner.acceptStylusHandwritingDelegation(
+                                    client, userId, delegatePackageName, delegatorPackageName,
+                                    flags),
+                    this::offload).get();
         } catch (InterruptedException e) {
             throw new RuntimeException(e);
         } catch (ExecutionException e) {
@@ -384,8 +375,7 @@
             @UserIdInt int userId,
             @NonNull String delegatePackageName,
             @NonNull String delegatorPackageName,
-            @InputMethodManager.HandwritingDelegateFlags int flags, IBooleanListener callback)
-            throws RemoteException {
+            @InputMethodManager.HandwritingDelegateFlags int flags, IBooleanListener callback) {
         offload(() -> mInner.acceptStylusHandwritingDelegationAsync(
                 client, userId, delegatePackageName, delegatorPackageName, flags, callback));
     }
@@ -401,52 +391,45 @@
     }
 
     @Override
-    public boolean isStylusHandwritingAvailableAsUser(int userId, boolean connectionless)
-            throws RemoteException {
+    public boolean isStylusHandwritingAvailableAsUser(int userId, boolean connectionless) {
         return mInner.isStylusHandwritingAvailableAsUser(userId, connectionless);
     }
 
-    @EnforcePermission("android.permission.TEST_INPUT_METHOD")
+    @IInputMethodManagerImpl.PermissionVerified("android.permission.TEST_INPUT_METHOD")
     @Override
-    public void addVirtualStylusIdForTestSession(IInputMethodClient client)
-            throws RemoteException {
+    public void addVirtualStylusIdForTestSession(IInputMethodClient client) {
         mInner.addVirtualStylusIdForTestSession(client);
     }
 
-    @EnforcePermission("android.permission.TEST_INPUT_METHOD")
+    @IInputMethodManagerImpl.PermissionVerified("android.permission.TEST_INPUT_METHOD")
     @Override
-    public void setStylusWindowIdleTimeoutForTest(IInputMethodClient client, long timeout)
-            throws RemoteException {
+    public void setStylusWindowIdleTimeoutForTest(IInputMethodClient client, long timeout) {
         mInner.setStylusWindowIdleTimeoutForTest(client, timeout);
     }
 
     @Override
-    public IImeTracker getImeTrackerService() throws RemoteException {
+    public IImeTracker getImeTrackerService() {
         return mInner.getImeTrackerService();
     }
 
     @BinderThread
     @Override
     public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
-            @Nullable FileDescriptor err,
-            @NonNull String[] args, @Nullable ShellCallback callback,
-            @NonNull ResultReceiver resultReceiver) throws RemoteException {
-        ((InputMethodManagerService) mInner).onShellCommand(
-                in, out, err, args, callback, resultReceiver);
+            @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback,
+            @NonNull ResultReceiver resultReceiver, @NonNull Binder self) {
+        mInner.onShellCommand(in, out, err, args, callback, resultReceiver, self);
     }
 
     @Override
-    protected void dump(@NonNull FileDescriptor fd,
-            @NonNull PrintWriter fout,
+    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
             @Nullable String[] args) {
-        ((InputMethodManagerService) mInner).dump(fd, fout, args);
+        mInner.dump(fd, fout, args);
     }
 
     private void sendOnStartInputResult(
             IInputMethodClient client, InputBindResult res, int startInputSeq) {
         synchronized (ImfLock.class) {
-            InputMethodManagerService service = (InputMethodManagerService) mInner;
-            final ClientState cs = service.getClientState(client);
+            final ClientState cs = mInner.getClientStateLocked(client);
             if (cs != null && cs.mClient != null) {
                 cs.mClient.onStartInputResult(res, startInputSeq);
             } else {
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 19562ef..dbdb155 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -950,13 +950,18 @@
                             && android.multiuser.Flags.enablePrivateSpaceFeatures()
                             && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) {
                         mHandler.post(() -> {
-                            UserProperties userProperties =
-                                    mUserManager.getUserProperties(UserHandle.of(userId));
-                            if (userProperties != null
-                                    && userProperties.getAllowStoppingUserWithDelayedLocking()) {
-                                int strongAuthRequired = LockPatternUtils.StrongAuthTracker
-                                        .getDefaultFlags(mContext);
-                                requireStrongAuth(strongAuthRequired, userId);
+                            try {
+                                UserProperties userProperties =
+                                        mUserManager.getUserProperties(UserHandle.of(userId));
+                                if (userProperties != null && userProperties
+                                        .getAllowStoppingUserWithDelayedLocking()) {
+                                    int strongAuthRequired = LockPatternUtils.StrongAuthTracker
+                                            .getDefaultFlags(mContext);
+                                    requireStrongAuth(strongAuthRequired, userId);
+                                }
+                            } catch (IllegalArgumentException e) {
+                                Slogf.d(TAG, "User %d does not exist or has been removed",
+                                        userId);
                             }
                         });
                     }
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index 0cd7654..dfb2b0a 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -157,6 +157,11 @@
     }
 
     @Override
+    public void expireTempEngaged() {
+        // NA as MediaSession2 doesn't support UserEngagementStates for FGS.
+    }
+
+    @Override
     public boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService,
             KeyEvent ke, int sequenceId, ResultReceiver cb) {
         // TODO(jaewan): Implement.
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 93f68a8..194ab04 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -24,6 +24,7 @@
 import static android.media.session.MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE;
 
 import android.Manifest;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -85,6 +86,8 @@
 import com.android.server.uri.UriGrantsManagerInternal;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -225,6 +228,49 @@
 
     private int mPolicies;
 
+    private @UserEngagementState int mUserEngagementState = USER_DISENGAGED;
+
+    @IntDef({USER_PERMANENTLY_ENGAGED, USER_TEMPORARY_ENGAGED, USER_DISENGAGED})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface UserEngagementState {}
+
+    /**
+     * Indicates that the session is active and in one of the user engaged states.
+     *
+     * @see #updateUserEngagedStateIfNeededLocked(boolean) ()
+     */
+    private static final int USER_PERMANENTLY_ENGAGED = 0;
+
+    /**
+     * Indicates that the session is active and in {@link PlaybackState#STATE_PAUSED} state.
+     *
+     * @see #updateUserEngagedStateIfNeededLocked(boolean) ()
+     */
+    private static final int USER_TEMPORARY_ENGAGED = 1;
+
+    /**
+     * Indicates that the session is either not active or in one of the user disengaged states
+     *
+     * @see #updateUserEngagedStateIfNeededLocked(boolean) ()
+     */
+    private static final int USER_DISENGAGED = 2;
+
+    /**
+     * Indicates the duration of the temporary engaged states.
+     *
+     * <p>Some {@link MediaSession} states like {@link PlaybackState#STATE_PAUSED} are temporarily
+     * engaged, meaning the corresponding session is only considered in an engaged state for the
+     * duration of this timeout, and only if coming from an engaged state.
+     *
+     * <p>For example, if a session is transitioning from a user-engaged state {@link
+     * PlaybackState#STATE_PLAYING} to a temporary user-engaged state {@link
+     * PlaybackState#STATE_PAUSED}, then the session will be considered in a user-engaged state for
+     * the duration of this timeout, starting at the transition instant. However, a temporary
+     * user-engaged state is not considered user-engaged when transitioning from a non-user engaged
+     * state {@link PlaybackState#STATE_STOPPED}.
+     */
+    private static final int TEMP_USER_ENGAGED_TIMEOUT = 600000;
+
     public MediaSessionRecord(
             int ownerPid,
             int ownerUid,
@@ -548,6 +594,7 @@
             mSessionCb.mCb.asBinder().unlinkToDeath(this, 0);
             mDestroyed = true;
             mPlaybackState = null;
+            updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true);
             mHandler.post(MessageHandler.MSG_DESTROYED);
         }
     }
@@ -559,6 +606,12 @@
         }
     }
 
+    @Override
+    public void expireTempEngaged() {
+        mHandler.removeCallbacks(mHandleTempEngagedSessionTimeout);
+        updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true);
+    }
+
     /**
      * Sends media button.
      *
@@ -1129,6 +1182,11 @@
                 }
             };
 
+    private final Runnable mHandleTempEngagedSessionTimeout =
+            () -> {
+                updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true);
+            };
+
     @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
     private static boolean componentNameExists(
             @NonNull ComponentName componentName, @NonNull Context context, int userId) {
@@ -1145,6 +1203,40 @@
         return !resolveInfos.isEmpty();
     }
 
+    private void updateUserEngagedStateIfNeededLocked(boolean isTimeoutExpired) {
+        int oldUserEngagedState = mUserEngagementState;
+        int newUserEngagedState;
+        if (!isActive() || mPlaybackState == null) {
+            newUserEngagedState = USER_DISENGAGED;
+        } else if (isActive() && mPlaybackState.isActive()) {
+            newUserEngagedState = USER_PERMANENTLY_ENGAGED;
+        } else if (mPlaybackState.getState() == PlaybackState.STATE_PAUSED) {
+            newUserEngagedState =
+                    oldUserEngagedState == USER_PERMANENTLY_ENGAGED || !isTimeoutExpired
+                            ? USER_TEMPORARY_ENGAGED
+                            : USER_DISENGAGED;
+        } else {
+            newUserEngagedState = USER_DISENGAGED;
+        }
+        if (oldUserEngagedState == newUserEngagedState) {
+            return;
+        }
+
+        if (newUserEngagedState == USER_TEMPORARY_ENGAGED) {
+            mHandler.postDelayed(mHandleTempEngagedSessionTimeout, TEMP_USER_ENGAGED_TIMEOUT);
+        } else if (oldUserEngagedState == USER_TEMPORARY_ENGAGED) {
+            mHandler.removeCallbacks(mHandleTempEngagedSessionTimeout);
+        }
+
+        boolean wasUserEngaged = oldUserEngagedState != USER_DISENGAGED;
+        boolean isNowUserEngaged = newUserEngagedState != USER_DISENGAGED;
+        mUserEngagementState = newUserEngagedState;
+        if (wasUserEngaged != isNowUserEngaged) {
+            mService.onSessionUserEngagementStateChange(
+                    /* mediaSessionRecord= */ this, /* isUserEngaged= */ isNowUserEngaged);
+        }
+    }
+
     private final class SessionStub extends ISession.Stub {
         @Override
         public void destroySession() throws RemoteException {
@@ -1182,8 +1274,10 @@
                         .logFgsApiEnd(ActivityManager.FOREGROUND_SERVICE_API_TYPE_MEDIA_PLAYBACK,
                                 callingUid, callingPid);
             }
-
-            mIsActive = active;
+            synchronized (mLock) {
+                mIsActive = active;
+                updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ false);
+            }
             long token = Binder.clearCallingIdentity();
             try {
                 mService.onSessionActiveStateChanged(MediaSessionRecord.this, mPlaybackState);
@@ -1341,6 +1435,7 @@
                     && TRANSITION_PRIORITY_STATES.contains(newState));
             synchronized (mLock) {
                 mPlaybackState = state;
+                updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ false);
             }
             final long token = Binder.clearCallingIdentity();
             try {
diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
index 0999199..b57b148 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
@@ -196,6 +196,12 @@
      */
     public abstract boolean isClosed();
 
+    /**
+     * Note: This method is only used for testing purposes If the session is temporary engaged, the
+     * timeout will expire and it will become disengaged.
+     */
+    public abstract void expireTempEngaged();
+
     @Override
     public final boolean equals(Object o) {
         if (this == o) return true;
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 53c32cf..74adf5e 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -367,11 +367,13 @@
             }
             boolean isUserEngaged = isUserEngaged(record, playbackState);
 
-            Log.d(TAG, "onSessionActiveStateChanged: "
-                    + "record=" + record
-                    + "playbackState=" + playbackState
-                    + "allowRunningInForeground=" + isUserEngaged);
-            setForegroundServiceAllowance(record, /* allowRunningInForeground= */ isUserEngaged);
+            Log.d(
+                    TAG,
+                    "onSessionActiveStateChanged:"
+                            + " record="
+                            + record
+                            + " playbackState="
+                            + playbackState);
             reportMediaInteractionEvent(record, isUserEngaged);
             mHandler.postSessionsChanged(record);
         }
@@ -479,11 +481,13 @@
             }
             user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority);
             boolean isUserEngaged = isUserEngaged(record, playbackState);
-            Log.d(TAG, "onSessionPlaybackStateChanged: "
-                    + "record=" + record
-                    + "playbackState=" + playbackState
-                    + "allowRunningInForeground=" + isUserEngaged);
-            setForegroundServiceAllowance(record, /* allowRunningInForeground= */ isUserEngaged);
+            Log.d(
+                    TAG,
+                    "onSessionPlaybackStateChanged:"
+                            + " record="
+                            + record
+                            + " playbackState="
+                            + playbackState);
             reportMediaInteractionEvent(record, isUserEngaged);
         }
     }
@@ -650,68 +654,112 @@
         session.close();
 
         Log.d(TAG, "destroySessionLocked: record=" + session);
-        setForegroundServiceAllowance(session, /* allowRunningInForeground= */ false);
+
         reportMediaInteractionEvent(session, /* userEngaged= */ false);
         mHandler.postSessionsChanged(session);
     }
 
-    private void setForegroundServiceAllowance(
-            MediaSessionRecordImpl record, boolean allowRunningInForeground) {
-        if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
-            return;
-        }
-        ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
-                record.getForegroundServiceDelegationOptions();
-        if (foregroundServiceDelegationOptions == null) {
-            return;
-        }
-        if (allowRunningInForeground) {
-            onUserSessionEngaged(record);
+    void onSessionUserEngagementStateChange(
+            MediaSessionRecordImpl mediaSessionRecord, boolean isUserEngaged) {
+        if (isUserEngaged) {
+            addUserEngagedSession(mediaSessionRecord);
+            startFgsIfSessionIsLinkedToNotification(mediaSessionRecord);
         } else {
-            onUserDisengaged(record);
+            removeUserEngagedSession(mediaSessionRecord);
+            stopFgsIfNoSessionIsLinkedToNotification(mediaSessionRecord);
         }
     }
 
-    private void onUserSessionEngaged(MediaSessionRecordImpl mediaSessionRecord) {
+    private void addUserEngagedSession(MediaSessionRecordImpl mediaSessionRecord) {
         synchronized (mLock) {
             int uid = mediaSessionRecord.getUid();
             mUserEngagedSessionsForFgs.putIfAbsent(uid, new HashSet<>());
             mUserEngagedSessionsForFgs.get(uid).add(mediaSessionRecord);
+        }
+    }
+
+    private void removeUserEngagedSession(MediaSessionRecordImpl mediaSessionRecord) {
+        synchronized (mLock) {
+            int uid = mediaSessionRecord.getUid();
+            Set<MediaSessionRecordImpl> mUidUserEngagedSessionsForFgs =
+                    mUserEngagedSessionsForFgs.get(uid);
+            if (mUidUserEngagedSessionsForFgs == null) {
+                return;
+            }
+
+            mUidUserEngagedSessionsForFgs.remove(mediaSessionRecord);
+            if (mUidUserEngagedSessionsForFgs.isEmpty()) {
+                mUserEngagedSessionsForFgs.remove(uid);
+            }
+        }
+    }
+
+    private void startFgsIfSessionIsLinkedToNotification(
+            MediaSessionRecordImpl mediaSessionRecord) {
+        Log.d(TAG, "startFgsIfSessionIsLinkedToNotification: record=" + mediaSessionRecord);
+        if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+            return;
+        }
+        synchronized (mLock) {
+            int uid = mediaSessionRecord.getUid();
             for (Notification mediaNotification : mMediaNotifications.getOrDefault(uid, Set.of())) {
                 if (mediaSessionRecord.isLinkedToNotification(mediaNotification)) {
-                    mActivityManagerInternal.startForegroundServiceDelegate(
-                            mediaSessionRecord.getForegroundServiceDelegationOptions(),
-                            /* connection= */ null);
+                    startFgsDelegate(mediaSessionRecord.getForegroundServiceDelegationOptions());
                     return;
                 }
             }
         }
     }
 
-    private void onUserDisengaged(MediaSessionRecordImpl mediaSessionRecord) {
+    private void startFgsDelegate(
+            ForegroundServiceDelegationOptions foregroundServiceDelegationOptions) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mActivityManagerInternal.startForegroundServiceDelegate(
+                    foregroundServiceDelegationOptions, /* connection= */ null);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    private void stopFgsIfNoSessionIsLinkedToNotification(
+            MediaSessionRecordImpl mediaSessionRecord) {
+        Log.d(TAG, "stopFgsIfNoSessionIsLinkedToNotification: record=" + mediaSessionRecord);
+        if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+            return;
+        }
         synchronized (mLock) {
             int uid = mediaSessionRecord.getUid();
-            if (mUserEngagedSessionsForFgs.containsKey(uid)) {
-                mUserEngagedSessionsForFgs.get(uid).remove(mediaSessionRecord);
-                if (mUserEngagedSessionsForFgs.get(uid).isEmpty()) {
-                    mUserEngagedSessionsForFgs.remove(uid);
-                }
+            ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
+                    mediaSessionRecord.getForegroundServiceDelegationOptions();
+            if (foregroundServiceDelegationOptions == null) {
+                return;
             }
 
-            boolean shouldStopFgs = true;
-            for (MediaSessionRecordImpl sessionRecord :
+            for (MediaSessionRecordImpl record :
                     mUserEngagedSessionsForFgs.getOrDefault(uid, Set.of())) {
-                for (Notification mediaNotification : mMediaNotifications.getOrDefault(uid,
-                        Set.of())) {
-                    if (sessionRecord.isLinkedToNotification(mediaNotification)) {
-                        shouldStopFgs = false;
+                for (Notification mediaNotification :
+                        mMediaNotifications.getOrDefault(uid, Set.of())) {
+                    if (record.isLinkedToNotification(mediaNotification)) {
+                        // A user engaged session linked with a media notification is found.
+                        // We shouldn't call stop FGS in this case.
+                        return;
                     }
                 }
             }
-            if (shouldStopFgs) {
-                mActivityManagerInternal.stopForegroundServiceDelegate(
-                        mediaSessionRecord.getForegroundServiceDelegationOptions());
-            }
+
+            stopFgsDelegate(foregroundServiceDelegationOptions);
+        }
+    }
+
+    private void stopFgsDelegate(
+            ForegroundServiceDelegationOptions foregroundServiceDelegationOptions) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mActivityManagerInternal.stopForegroundServiceDelegate(
+                    foregroundServiceDelegationOptions);
+        } finally {
+            Binder.restoreCallingIdentity(token);
         }
     }
 
@@ -2502,7 +2550,6 @@
             }
             MediaSessionRecord session = null;
             MediaButtonReceiverHolder mediaButtonReceiverHolder = null;
-
             if (mCustomMediaKeyDispatcher != null) {
                 MediaSession.Token token = mCustomMediaKeyDispatcher.getMediaSession(
                         keyEvent, uid, asSystemService);
@@ -2630,6 +2677,18 @@
                     && streamType <= AudioManager.STREAM_NOTIFICATION;
         }
 
+        @Override
+        public void expireTempEngagedSessions() {
+            synchronized (mLock) {
+                for (Set<MediaSessionRecordImpl> uidSessions :
+                        mUserEngagedSessionsForFgs.values()) {
+                    for (MediaSessionRecordImpl sessionRecord : uidSessions) {
+                        sessionRecord.expireTempEngaged();
+                    }
+                }
+            }
+        }
+
         private class MediaKeyListenerResultReceiver extends ResultReceiver implements Runnable {
             private final String mPackageName;
             private final int mPid;
@@ -3127,7 +3186,6 @@
             super.onNotificationPosted(sbn);
             Notification postedNotification = sbn.getNotification();
             int uid = sbn.getUid();
-
             if (!postedNotification.isMediaNotification()) {
                 return;
             }
@@ -3138,11 +3196,9 @@
                         mUserEngagedSessionsForFgs.getOrDefault(uid, Set.of())) {
                     ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
                             mediaSessionRecord.getForegroundServiceDelegationOptions();
-                    if (mediaSessionRecord.isLinkedToNotification(postedNotification)
-                            && foregroundServiceDelegationOptions != null) {
-                        mActivityManagerInternal.startForegroundServiceDelegate(
-                                foregroundServiceDelegationOptions,
-                                /* connection= */ null);
+                    if (foregroundServiceDelegationOptions != null
+                            && mediaSessionRecord.isLinkedToNotification(postedNotification)) {
+                        startFgsDelegate(foregroundServiceDelegationOptions);
                         return;
                     }
                 }
@@ -3173,21 +3229,7 @@
                     return;
                 }
 
-                boolean shouldStopFgs = true;
-                for (MediaSessionRecordImpl mediaSessionRecord :
-                        mUserEngagedSessionsForFgs.getOrDefault(uid, Set.of())) {
-                    for (Notification mediaNotification :
-                            mMediaNotifications.getOrDefault(uid, Set.of())) {
-                        if (mediaSessionRecord.isLinkedToNotification(mediaNotification)) {
-                            shouldStopFgs = false;
-                        }
-                    }
-                }
-                if (shouldStopFgs
-                        && notificationRecord.getForegroundServiceDelegationOptions() != null) {
-                    mActivityManagerInternal.stopForegroundServiceDelegate(
-                            notificationRecord.getForegroundServiceDelegationOptions());
-                }
+                stopFgsIfNoSessionIsLinkedToNotification(notificationRecord);
             }
         }
 
diff --git a/services/core/java/com/android/server/media/MediaShellCommand.java b/services/core/java/com/android/server/media/MediaShellCommand.java
index a563808..a20de31 100644
--- a/services/core/java/com/android/server/media/MediaShellCommand.java
+++ b/services/core/java/com/android/server/media/MediaShellCommand.java
@@ -92,6 +92,8 @@
                 runMonitor();
             } else if (cmd.equals("volume")) {
                 runVolume();
+            } else if (cmd.equals("expire-temp-engaged-sessions")) {
+                expireTempEngagedSessions();
             } else {
                 showError("Error: unknown command '" + cmd + "'");
                 return -1;
@@ -367,4 +369,8 @@
     private void runVolume() throws Exception {
         VolumeCtrl.run(this);
     }
+
+    private void expireTempEngagedSessions() throws Exception {
+        mSessionService.expireTempEngagedSessions();
+    }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 88f86cc..a0902cd 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -8967,8 +8967,7 @@
                 : mDisplayContent.getDisplayInfo();
         final Task task = getTask();
         task.calculateInsetFrames(mTmpBounds /* outNonDecorBounds */,
-                outStableBounds /* outStableBounds */, parentBounds /* bounds */, di,
-                true /* useLegacyInsetsForStableBounds */);
+                outStableBounds /* outStableBounds */, parentBounds /* bounds */, di);
         final int orientationWithInsets = outStableBounds.height() >= outStableBounds.width()
                 ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
         // If orientation does not match the orientation with insets applied, then a
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index e157318..f8aa69b 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -912,7 +912,7 @@
     @Override
     public void grantInputChannel(int displayId, SurfaceControl surface,
             IBinder clientToken, @Nullable InputTransferToken hostInputTransferToken, int flags,
-            int privateFlags, int type, int inputFeatures, IBinder windowToken,
+            int privateFlags, int inputFeatures, int type, IBinder windowToken,
             InputTransferToken inputTransferToken, String inputHandleName,
             InputChannel outInputChannel) {
         if (hostInputTransferToken == null && !mCanAddInternalSystemWindow) {
@@ -925,7 +925,7 @@
         try {
             mService.grantInputChannel(this, mUid, mPid, displayId, surface, clientToken,
                     hostInputTransferToken, flags, mCanAddInternalSystemWindow ? privateFlags : 0,
-                    type, inputFeatures, windowToken, inputTransferToken, inputHandleName,
+                    inputFeatures, type, windowToken, inputTransferToken, inputHandleName,
                     outInputChannel);
         } finally {
             Binder.restoreCallingIdentity(identity);
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 129af90..218fb7f 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2309,8 +2309,7 @@
                 // area, i.e. the screen area without the system bars.
                 // The non decor inset are areas that could never be removed in Honeycomb. See
                 // {@link WindowManagerPolicy#getNonDecorInsetsLw}.
-                calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di,
-                        false /* useLegacyInsetsForStableBounds */);
+                calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di);
             } else {
                 // Apply the given non-decor and stable insets to calculate the corresponding bounds
                 // for screen size of configuration.
@@ -2408,11 +2407,9 @@
      * @param outNonDecorBounds where to place bounds with non-decor insets applied.
      * @param outStableBounds where to place bounds with stable insets applied.
      * @param bounds the bounds to inset.
-     * @param useLegacyInsetsForStableBounds {@code true} if we need to use the legacy insets frame
-     *                for apps targeting U or before when calculating stable bounds.
      */
     void calculateInsetFrames(Rect outNonDecorBounds, Rect outStableBounds, Rect bounds,
-            DisplayInfo displayInfo, boolean useLegacyInsetsForStableBounds) {
+            DisplayInfo displayInfo) {
         outNonDecorBounds.set(bounds);
         outStableBounds.set(bounds);
         if (mDisplayContent == null) {
@@ -2424,11 +2421,7 @@
         final DisplayPolicy.DecorInsets.Info info = policy.getDecorInsetsInfo(
                 displayInfo.rotation, displayInfo.logicalWidth, displayInfo.logicalHeight);
         intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, info.mNonDecorInsets);
-        if (!useLegacyInsetsForStableBounds) {
-            intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mConfigInsets);
-        } else {
-            intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mOverrideConfigInsets);
-        }
+        intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mConfigInsets);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 222abc3..ce53290 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -565,6 +565,9 @@
         if (isTransientCollect(ar)) {
             return true;
         }
+        for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) {
+            if (mWaitingTransitions.get(i).isTransientLaunch(ar)) return true;
+        }
         for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
             if (mPlayingTransitions.get(i).isTransientLaunch(ar)) return true;
         }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 0186006..42fe3a7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1630,6 +1630,7 @@
         assertTrue(controller.mWaitingTransitions.contains(transition));
         assertTrue(controller.isTransientHide(appTask));
         assertTrue(controller.isTransientVisible(appTask));
+        assertTrue(controller.isTransientLaunch(recent));
     }
 
     @Test