Merge "Honor the default wallet role in QuickAccessWallet." into main
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 20b0932..1867a17 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -847,16 +847,18 @@
      * of the camera resolutions advertised by
      * {@link StreamConfigurationMap#getOutputSizes}.</p>
      *
-     * <p>Device-specific extensions currently support at most two
+     * <p>Device-specific extensions currently support at most three
      * multi-frame capture surface formats. ImageFormat.JPEG will be supported by all
-     * extensions and ImageFormat.YUV_420_888 may or may not be supported.</p>
+     * extensions while ImageFormat.YUV_420_888 and ImageFormat.JPEG_R may or may not be
+     * supported.</p>
      *
      * @param extension the extension type
      * @param format    device-specific extension output format
      * @return non-modifiable list of available sizes or an empty list if the format is not
      * supported.
-     * @throws IllegalArgumentException in case of format different from ImageFormat.JPEG /
-     *                                  ImageFormat.YUV_420_888; or unsupported extension.
+     * @throws IllegalArgumentException in case of format different from ImageFormat.JPEG,
+     *                                  ImageFormat.YUV_420_888, ImageFormat.JPEG_R; or
+     *                                  unsupported extension.
      */
     public @NonNull
     List<Size> getExtensionSupportedSizes(@Extension int extension, int format) {
@@ -940,14 +942,16 @@
      * @param format            device-specific extension output format
      * @return the range of estimated minimal and maximal capture latency in milliseconds
      * or null if no capture latency info can be provided
-     * @throws IllegalArgumentException in case of format different from {@link ImageFormat#JPEG} /
-     *                                  {@link ImageFormat#YUV_420_888}; or unsupported extension.
+     * @throws IllegalArgumentException in case of format different from {@link ImageFormat#JPEG},
+     *                                  {@link ImageFormat#YUV_420_888}, {@link ImageFormat#JPEG_R};
+     *                                  or unsupported extension.
      */
     public @Nullable Range<Long> getEstimatedCaptureLatencyRangeMillis(@Extension int extension,
             @NonNull Size captureOutputSize, @ImageFormat.Format int format) {
         switch (format) {
             case ImageFormat.YUV_420_888:
             case ImageFormat.JPEG:
+            case ImageFormat.JPEG_R:
                 //No op
                 break;
             default:
@@ -994,6 +998,10 @@
                     // specific and cannot be estimated accurately enough.
                     return  null;
                 }
+                if (format == ImageFormat.JPEG_R) {
+                    // JpegR/UltraHDR is not supported for basic extensions
+                    return null;
+                }
 
                 LatencyRange latencyRange = extenders.second.getEstimatedCaptureLatencyRange(sz);
                 if (latencyRange != null) {
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index a1885ae..bf1a596 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -82,3 +82,10 @@
   description: "A feature flag that implement inter character justification."
   bug: "283193133"
 }
+
+flag {
+  name: "escape_clears_focus"
+  namespace: "text"
+  description: "Feature flag for clearing focus when the escape key is pressed."
+  bug: "312921137"
+}
diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
index 2dfeae3..80aad60 100644
--- a/core/java/android/webkit/WebViewClient.java
+++ b/core/java/android/webkit/WebViewClient.java
@@ -65,6 +65,14 @@
      * {@code true} causes the current WebView to abort loading the URL, while returning
      * {@code false} causes the WebView to continue loading the URL as usual.
      *
+     * <p>This callback is not called for all page navigations. In particular, this is not called
+     * for navigations which the app initiated with {@code loadUrl()}: this callback would not serve
+     * a purpose in this case, because the app already knows about the navigation. This callback
+     * lets the app know about navigations initiated by the web page (such as navigations initiated
+     * by JavaScript code), by the user (such as when the user taps on a link), or by an HTTP
+     * redirect (ex. if {@code loadUrl("foo.com")} redirects to {@code "bar.com"} because of HTTP
+     * 301).
+     *
      * <p class="note"><b>Note:</b> Do not call {@link WebView#loadUrl(String)} with the request's
      * URL and then return {@code true}. This unnecessarily cancels the current load and starts a
      * new load with the same URL. The correct way to continue loading a given URL is to simply
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 6da6a64..e812f85 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -9589,6 +9589,23 @@
                 }
                 break;
 
+            case KeyEvent.KEYCODE_ESCAPE:
+                if (com.android.text.flags.Flags.escapeClearsFocus() && event.hasNoModifiers()) {
+                    if (mEditor != null && mEditor.getTextActionMode() != null) {
+                        stopTextActionMode();
+                        return KEY_EVENT_HANDLED;
+                    }
+                    if (hasFocus()) {
+                        clearFocus();
+                        InputMethodManager imm = getInputMethodManager();
+                        if (imm != null) {
+                            imm.hideSoftInputFromView(this, 0);
+                        }
+                        return KEY_EVENT_HANDLED;
+                    }
+                }
+                break;
+
             case KeyEvent.KEYCODE_CUT:
                 if (event.hasNoModifiers() && canCut()) {
                     if (onTextContextMenuItem(ID_CUT)) {
diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index f67eefa..51890ec 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -36,13 +36,6 @@
 }
 
 flag {
-    name: "bal_return_correct_code_if_caller_is_persistent_system_process"
-    namespace: "responsible_apis"
-    description: "Split visibility check and return a better status code in case of system process."
-    bug: "171459802"
-}
-
-flag {
     name: "bal_improve_real_caller_visibility_check"
     namespace: "responsible_apis"
     description: "Prevent a task to restart based on a visible window during task switch."
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 556a315..409f15b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -624,17 +624,10 @@
             modifier =
                 modifier.align(Alignment.Center).allowGestures(allowed = !viewModel.isEditMode),
             factory = { context ->
-                // The AppWidgetHostView will inherit the interaction handler from the
-                // AppWidgetHost. So set the interaction handler here before creating the view, and
-                // then clear it after the view is created. This is a workaround due to the fact
-                // that the interaction handler cannot be specified when creating the view,
-                // and there are race conditions if it is set after the view is created.
-                model.appWidgetHost.setInteractionHandler(viewModel.getInteractionHandler())
                 val view =
                     model.appWidgetHost
                         .createViewForCommunal(context, model.appWidgetId, model.providerInfo)
                         .apply { updateAppWidgetSize(Bundle.EMPTY, listOf(size)) }
-                model.appWidgetHost.setInteractionHandler(null)
                 // Remove the extra padding applied to AppWidgetHostView to allow widgets to
                 // occupy the entire box. The added padding is now adjusted to leave only sufficient
                 // space for displaying the outline around the box when the widget is selected.
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 2bfa7d9..cea49e1 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -27,6 +27,7 @@
 import android.text.format.DateFormat
 import android.util.AttributeSet
 import android.util.MathUtils.constrainedMap
+import android.view.View
 import android.widget.TextView
 import com.android.app.animation.Interpolators
 import com.android.internal.annotations.VisibleForTesting
@@ -51,7 +52,7 @@
     context: Context,
     attrs: AttributeSet? = null,
     defStyleAttr: Int = 0,
-    defStyleRes: Int = 0
+    defStyleRes: Int = 0,
 ) : TextView(context, attrs, defStyleAttr, defStyleRes) {
     // To protect us from issues from this being null while the TextView constructor is running, we
     // implement the get method and ensure a value is returned before initialization is complete.
@@ -61,6 +62,9 @@
         get() = logger.buffer
         set(value) { logger = Logger(value, TAG) }
 
+    var hasCustomPositionUpdatedAnimation: Boolean = false
+    var migratedClocks: Boolean = false
+
     private val time = Calendar.getInstance()
 
     private val dozingWeightInternal: Int
@@ -193,9 +197,18 @@
         } else {
             animator.updateLayout(layout)
         }
+        if (migratedClocks && hasCustomPositionUpdatedAnimation) {
+            // Expand width to avoid clock being clipped during stepping animation
+            setMeasuredDimension(measuredWidth +
+                    MeasureSpec.getSize(widthMeasureSpec) / 2, measuredHeight)
+        }
     }
 
     override fun onDraw(canvas: Canvas) {
+        if (migratedClocks && hasCustomPositionUpdatedAnimation) {
+            canvas.save()
+            canvas.translate((parent as View).measuredWidth / 4F, 0F)
+        }
         logger.d({ "onDraw($str1)"}) { str1 = text.toString() }
         // Use textAnimator to render text if animation is enabled.
         // Otherwise default to using standard draw functions.
@@ -205,6 +218,9 @@
         } else {
             super.onDraw(canvas)
         }
+        if (migratedClocks && hasCustomPositionUpdatedAnimation) {
+            canvas.restore()
+        }
     }
 
     override fun invalidate() {
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index 99d3216..001e3a5 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -193,6 +193,8 @@
             ClockFaceConfig(hasCustomPositionUpdatedAnimation = hasStepClockAnimation)
 
         init {
+            view.migratedClocks = migratedClocks
+            view.hasCustomPositionUpdatedAnimation = hasStepClockAnimation
             animations = LargeClockAnimations(view, 0f, 0f)
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
index e904236..3aa99c4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
@@ -16,44 +16,52 @@
 
 package com.android.systemui.communal.ui.widgets
 
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.widgets.CommunalAppWidgetHost
 import com.android.systemui.communal.widgets.CommunalAppWidgetHostView
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @SmallTest
+@RunWithLooper(setAsMainLooper = true)
 @RunWith(AndroidJUnit4::class)
 class CommunalAppWidgetHostTest : SysuiTestCase() {
-    private val kosmos = testKosmos()
-    private val testScope = kosmos.testScope
 
+    private lateinit var testableLooper: TestableLooper
     private lateinit var underTest: CommunalAppWidgetHost
 
     @Before
     fun setUp() {
-        underTest = CommunalAppWidgetHost(context = context, hostId = 116)
+        testableLooper = TestableLooper.get(this)
+        underTest =
+            CommunalAppWidgetHost(
+                context = context,
+                hostId = 116,
+                interactionHandler = mock(),
+                looper = testableLooper.looper
+            )
     }
 
     @Test
-    fun createViewForCommunal_returnCommunalAppWidgetView() =
-        testScope.runTest {
-            val appWidgetId = 789
-            val view =
-                underTest.createViewForCommunal(
-                    context = context,
-                    appWidgetId = appWidgetId,
-                    appWidget = null
-                )
-            assertThat(view).isInstanceOf(CommunalAppWidgetHostView::class.java)
-            assertThat(view).isNotNull()
-            assertThat(view.appWidgetId).isEqualTo(appWidgetId)
-        }
+    fun createViewForCommunal_returnCommunalAppWidgetView() {
+        val appWidgetId = 789
+        val view =
+            underTest.createViewForCommunal(
+                context = context,
+                appWidgetId = appWidgetId,
+                appWidget = null
+            )
+        testableLooper.processAllMessages()
+
+        assertThat(view).isInstanceOf(CommunalAppWidgetHostView::class.java)
+        assertThat(view).isNotNull()
+        assertThat(view.appWidgetId).isEqualTo(appWidgetId)
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 125ede4..867a48d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -41,7 +41,6 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -51,7 +50,6 @@
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class CommunalEditModeViewModelTest : SysuiTestCase() {
@@ -134,19 +132,6 @@
         }
 
     @Test
-    fun interactionHandlerIgnoresClicks() {
-        val interactionHandler = underTest.getInteractionHandler()
-        assertThat(
-                interactionHandler.onInteraction(
-                    /* view = */ mock(),
-                    /* pendingIntent = */ mock(),
-                    /* response = */ mock()
-                )
-            )
-            .isEqualTo(false)
-    }
-
-    @Test
     fun reorderWidget_uiEventLogging_start() {
         underTest.onReorderWidgetStart()
         verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_START)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index f9cfc37..9ac21dd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -32,7 +32,6 @@
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS
-import com.android.systemui.communal.widgets.WidgetInteractionHandler
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.media.controls.ui.MediaHierarchyManager
@@ -90,7 +89,6 @@
             CommunalViewModel(
                 testScope,
                 withDeps.communalInteractor,
-                WidgetInteractionHandler(mock()),
                 withDeps.tutorialInteractor,
                 mediaHost,
             )
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
new file mode 100644
index 0000000..69ff5ab
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+import android.app.PendingIntent
+import android.content.Intent
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.RemoteViews.RemoteResponse
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.util.mockito.eq
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.isNull
+import org.mockito.Mockito.notNull
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WidgetInteractionHandlerTest : SysuiTestCase() {
+    @Mock private lateinit var activityStarter: ActivityStarter
+
+    private lateinit var underTest: WidgetInteractionHandler
+
+    private val testIntent =
+        PendingIntent.getActivity(
+            context,
+            /* requestCode = */ 0,
+            Intent("action"),
+            PendingIntent.FLAG_IMMUTABLE
+        )
+    private val testResponse = RemoteResponse.fromPendingIntent(testIntent)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        underTest = WidgetInteractionHandler(activityStarter)
+    }
+
+    @Test
+    fun launchAnimatorIsUsedForWidgetView() {
+        val parent = FrameLayout(context)
+        val view = CommunalAppWidgetHostView(context)
+        parent.addView(view)
+
+        underTest.onInteraction(view, testIntent, testResponse)
+
+        verify(activityStarter)
+            .startPendingIntentMaybeDismissingKeyguard(
+                eq(testIntent),
+                isNull(),
+                notNull(),
+            )
+    }
+
+    @Test
+    fun launchAnimatorIsNotUsedForRegularView() {
+        val parent = FrameLayout(context)
+        val view = View(context)
+        parent.addView(view)
+
+        underTest.onInteraction(view, testIntent, testResponse)
+
+        verify(activityStarter)
+            .startPendingIntentMaybeDismissingKeyguard(eq(testIntent), isNull(), isNull())
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
index 8d04e3d..ce798ba 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
@@ -26,3 +26,20 @@
     importantForAccessibility =
         if (value) View.IMPORTANT_FOR_ACCESSIBILITY_YES else View.IMPORTANT_FOR_ACCESSIBILITY_NO
 }
+
+/**
+ * Can be used to find the nearest parent of a view of a particular type.
+ *
+ * Usage:
+ * ```
+ * val textView = view.getNearestParent<TextView>()
+ * ```
+ */
+inline fun <reified T : View> View.getNearestParent(): T? {
+    var view: Any? = this
+    while (view is View) {
+        if (view is T) return view
+        view = view.parent
+    }
+    return null
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
index 52f42c1..ab0a2d0d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
@@ -20,8 +20,10 @@
 import android.appwidget.AppWidgetManager
 import android.content.Context
 import android.content.res.Resources
+import android.os.Looper
 import com.android.systemui.communal.shared.CommunalWidgetHost
 import com.android.systemui.communal.widgets.CommunalAppWidgetHost
+import com.android.systemui.communal.widgets.WidgetInteractionHandler
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
@@ -48,8 +50,12 @@
 
         @SysUISingleton
         @Provides
-        fun provideCommunalAppWidgetHost(@Application context: Context): CommunalAppWidgetHost {
-            return CommunalAppWidgetHost(context, APP_WIDGET_HOST_ID)
+        fun provideCommunalAppWidgetHost(
+            @Application context: Context,
+            interactionHandler: WidgetInteractionHandler,
+            @Main looper: Looper,
+        ): CommunalAppWidgetHost {
+            return CommunalAppWidgetHost(context, APP_WIDGET_HOST_ID, interactionHandler, looper)
         }
 
         @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index f1b16c5..acc7981 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.communal.ui.viewmodel
 
 import android.content.ComponentName
-import android.widget.RemoteViews
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalSceneKey
@@ -103,9 +102,6 @@
     /** Called as the UI requests to dismiss the CTA tile. */
     open fun onDismissCtaTile() {}
 
-    /** Gets the interaction handler used to handle taps on a remote view */
-    abstract fun getInteractionHandler(): RemoteViews.InteractionHandler
-
     /** Called as the user starts dragging a widget to reorder. */
     open fun onReorderWidgetStart() {}
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index c69fa6f..237a0c0 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.communal.ui.viewmodel
 
-import android.widget.RemoteViews
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.model.CommunalContentModel
@@ -60,11 +59,6 @@
     override fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) =
         communalInteractor.updateWidgetOrder(widgetIdToPriorityMap)
 
-    override fun getInteractionHandler(): RemoteViews.InteractionHandler {
-        // Ignore all interactions in edit mode.
-        return RemoteViews.InteractionHandler { _, _, _ -> false }
-    }
-
     override fun onReorderWidgetStart() {
         // Clear selection status
         setSelectedIndex(null)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index d619362..2bb9d0e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -16,11 +16,9 @@
 
 package com.android.systemui.communal.ui.viewmodel
 
-import android.widget.RemoteViews
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
 import com.android.systemui.communal.domain.model.CommunalContentModel
-import com.android.systemui.communal.widgets.WidgetInteractionHandler
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.media.controls.ui.MediaHierarchyManager
@@ -48,7 +46,6 @@
 constructor(
     @Application private val scope: CoroutineScope,
     private val communalInteractor: CommunalInteractor,
-    private val interactionHandler: WidgetInteractionHandler,
     tutorialInteractor: CommunalTutorialInteractor,
     @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
 ) : BaseCommunalViewModel(communalInteractor, mediaHost) {
@@ -93,8 +90,6 @@
         }
     }
 
-    override fun getInteractionHandler(): RemoteViews.InteractionHandler = interactionHandler
-
     override fun onHidePopupAfterDismissCta() {
         cancelDelayedPopupHiding()
         setPopupOnDismissCtaVisibility(false)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
index 003c9d5..61db026 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
@@ -20,9 +20,16 @@
 import android.appwidget.AppWidgetHostView
 import android.appwidget.AppWidgetProviderInfo
 import android.content.Context
+import android.os.Looper
+import android.widget.RemoteViews
 
 /** Communal app widget host that creates a [CommunalAppWidgetHostView]. */
-class CommunalAppWidgetHost(context: Context, hostId: Int) : AppWidgetHost(context, hostId) {
+class CommunalAppWidgetHost(
+    context: Context,
+    hostId: Int,
+    interactionHandler: RemoteViews.InteractionHandler,
+    looper: Looper
+) : AppWidgetHost(context, hostId, interactionHandler, looper) {
     override fun onCreateView(
         context: Context,
         appWidgetId: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
index 2b7d823..840c3a8 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
@@ -22,9 +22,17 @@
 import android.graphics.Rect
 import android.view.View
 import android.view.ViewOutlineProvider
+import com.android.systemui.animation.LaunchableView
+import com.android.systemui.animation.LaunchableViewDelegate
 
 /** AppWidgetHostView that displays in communal hub with support for rounded corners. */
-class CommunalAppWidgetHostView(context: Context) : AppWidgetHostView(context) {
+class CommunalAppWidgetHostView(context: Context) : AppWidgetHostView(context), LaunchableView {
+    private val launchableViewDelegate =
+        LaunchableViewDelegate(
+            this,
+            superSetVisibility = { super.setVisibility(it) },
+        )
+
     // Mutable corner radius.
     var enforcedCornerRadius: Float
 
@@ -73,4 +81,9 @@
         outlineProvider = ViewOutlineProvider.BACKGROUND
         clipToOutline = false
     }
+
+    override fun setShouldBlockVisibilityChanges(block: Boolean) =
+        launchableViewDelegate.setShouldBlockVisibilityChanges(block)
+
+    override fun setVisibility(visibility: Int) = launchableViewDelegate.setVisibility(visibility)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
index c8db70b..afa7fa9 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
@@ -19,6 +19,8 @@
 import android.app.PendingIntent
 import android.view.View
 import android.widget.RemoteViews
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.common.ui.view.getNearestParent
 import com.android.systemui.plugins.ActivityStarter
 import javax.inject.Inject
 
@@ -33,17 +35,19 @@
         response: RemoteViews.RemoteResponse
     ): Boolean =
         when {
-            pendingIntent.isActivity -> startActivity(pendingIntent)
+            pendingIntent.isActivity -> startActivity(view, pendingIntent)
             else ->
                 RemoteViews.startPendingIntent(view, pendingIntent, response.getLaunchOptions(view))
         }
 
-    private fun startActivity(pendingIntent: PendingIntent): Boolean {
+    private fun startActivity(view: View, pendingIntent: PendingIntent): Boolean {
+        val hostView = view.getNearestParent<CommunalAppWidgetHostView>()
+        val animationController = hostView?.let(ActivityLaunchAnimator.Controller::fromView)
+
         activityStarter.startPendingIntentMaybeDismissingKeyguard(
-            /* intent = */ pendingIntent,
+            pendingIntent,
             /* intentSentUiThreadCallback = */ null,
-            // TODO(b/318758390): Properly animate activities started from widgets.
-            /* animationController = */ null
+            animationController
         )
         return true
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt
index f33aed0..f2b28d9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt
@@ -29,8 +29,4 @@
     ConstraintLayout(
         context,
         attrs,
-    ) {
-    init {
-        clipChildren = false
-    }
-}
+    )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index 226a957..bd659d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
 import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
 import javax.inject.Inject
 
 /**
@@ -61,6 +62,7 @@
         sensitiveContentCoordinator: SensitiveContentCoordinator,
         dismissibilityCoordinator: DismissibilityCoordinator,
         dreamCoordinator: DreamCoordinator,
+        statsLoggerCoordinator: NotificationStatsLoggerCoordinator,
 ) : NotifCoordinators {
 
     private val mCoreCoordinators: MutableList<CoreCoordinator> = ArrayList()
@@ -104,6 +106,10 @@
             mCoordinators.add(dreamCoordinator)
         }
 
+        if (NotificationsLiveDataStoreRefactor.isEnabled) {
+            mCoordinators.add(statsLoggerCoordinator)
+        }
+
         // Manually add Ordered Sections
         mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
         mOrderedSections.add(colorizedFgsCoordinator.sectioner) // ForegroundService
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinator.kt
new file mode 100644
index 0000000..6e81d93
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinator.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationStatsLogger
+import java.util.Optional
+import javax.inject.Inject
+
+@CoordinatorScope
+class NotificationStatsLoggerCoordinator
+@Inject
+constructor(private val loggerOptional: Optional<NotificationStatsLogger>) : Coordinator {
+
+    private val collectionListener =
+        object : NotifCollectionListener {
+            override fun onEntryUpdated(entry: NotificationEntry) {
+                super.onEntryUpdated(entry)
+                loggerOptional.ifPresent { it.onNotificationUpdated(entry.key) }
+            }
+
+            override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+                super.onEntryRemoved(entry, reason)
+                loggerOptional.ifPresent { it.onNotificationRemoved(entry.key) }
+            }
+        }
+    override fun attach(pipeline: NotifPipeline) {
+        if (NotificationsLiveDataStoreRefactor.isUnexpectedlyInLegacyMode()) {
+            return
+        }
+        pipeline.addCollectionListener(collectionListener)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index ae77288..0bc8e68 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -142,7 +142,7 @@
 
     /** Binds to [NotificationIconContainer.setAnimationsEnabled] */
     private suspend fun Flow<Boolean>.bindAnimationsEnabled(view: NotificationIconContainer) {
-        collect(view::setAnimationsEnabled)
+        collectTracingEach("NIC#bindAnimationsEnabled", view::setAnimationsEnabled)
     }
 
     private suspend fun NotificationIconContainerStatusBarViewModel.bindIsolatedIcon(
@@ -151,12 +151,12 @@
     ) {
         coroutineScope {
             launch {
-                isolatedIconLocation.collect { location ->
+                isolatedIconLocation.collectTracingEach("NIC#isolatedIconLocation") { location ->
                     view.setIsolatedIconLocation(location, true)
                 }
             }
             launch {
-                isolatedIcon.collect { iconInfo ->
+                isolatedIcon.collectTracingEach("NIC#showIconIsolated") { iconInfo ->
                     val iconView = iconInfo.value?.let { viewStore.iconView(it.notifKey) }
                     if (iconInfo.isAnimating) {
                         view.showIconIsolatedAnimated(iconView, iconInfo::stopAnimating)
@@ -214,7 +214,7 @@
         val failedBindings = mutableSetOf<String>()
         val boundViewsByNotifKey = ArrayMap<String, Pair<StatusBarIconView, Job>>()
         var prevIcons = NotificationIconsViewData()
-        collectTracingEach("NotifIconContainer#bindIcons") { iconsData: NotificationIconsViewData ->
+        collectTracingEach("NIC#bindIcons") { iconsData: NotificationIconsViewData ->
             val iconsDiff = NotificationIconsViewData.computeDifference(iconsData, prevIcons)
             prevIcons = iconsData
 
@@ -265,7 +265,11 @@
                             Pair(
                                 sbiv,
                                 launch {
-                                    launch { layoutParams.collect { sbiv.layoutParams = it } }
+                                    launch {
+                                        layoutParams.collectTracingEach("SBIV#bindLayoutParams") {
+                                            sbiv.layoutParams = it
+                                        }
+                                    }
                                     bindIcon(notifKey, sbiv)
                                 },
                             )
@@ -369,6 +373,7 @@
         )
     }
 
-private suspend fun <T> Flow<T>.collectTracingEach(tag: String, collector: (T) -> Unit) {
-    collect { traceSection(tag) { collector(it) } }
-}
+private suspend inline fun <T> Flow<T>.collectTracingEach(
+    tag: String,
+    crossinline collector: (T) -> Unit,
+) = collect { traceSection(tag) { collector(it) } }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt
index 0331654..bfeaced 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt
@@ -18,6 +18,7 @@
 
 import android.graphics.Rect
 import android.view.View
+import com.android.app.tracing.traceSection
 import com.android.internal.util.ContrastColorUtil
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.StatusBarIconView
@@ -33,18 +34,18 @@
     //  view-model (which, at the time of this writing, does not yet exist).
 
     suspend fun bindColor(view: StatusBarIconView, color: Flow<Int>) {
-        color.collect { color ->
+        color.collectTracingEach("SBIV#bindColor") { color ->
             view.staticDrawableColor = color
             view.setDecorColor(color)
         }
     }
 
     suspend fun bindTintAlpha(view: StatusBarIconView, tintAlpha: Flow<Float>) {
-        tintAlpha.collect { amt -> view.setTintAlpha(amt) }
+        tintAlpha.collectTracingEach("SBIV#bindTintAlpha") { amt -> view.setTintAlpha(amt) }
     }
 
     suspend fun bindAnimationsEnabled(view: StatusBarIconView, allowAnimation: Flow<Boolean>) {
-        allowAnimation.collect(view::setAllowAnimation)
+        allowAnimation.collectTracingEach("SBIV#bindAnimationsEnabled", view::setAllowAnimation)
     }
 
     suspend fun bindIconColors(
@@ -52,7 +53,7 @@
         iconColors: Flow<NotificationIconColors>,
         contrastColorUtil: ContrastColorUtil,
     ) {
-        iconColors.collect { colors ->
+        iconColors.collectTracingEach("SBIV#bindIconColors") { colors ->
             val isPreL = java.lang.Boolean.TRUE == view.getTag(R.id.icon_is_pre_L)
             val isColorized = !isPreL || NotificationUtils.isGrayscale(view, contrastColorUtil)
             view.staticDrawableColor =
@@ -73,3 +74,8 @@
             /* bottom = */ top + height,
         )
     }
+
+private suspend inline fun <T> Flow<T>.collectTracingEach(
+    tag: String,
+    crossinline collector: (T) -> Unit,
+) = collect { traceSection(tag) { collector(it) } }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLogger.kt
index 5418616..365c02f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLogger.kt
@@ -29,9 +29,7 @@
         activeNotifications: List<ActiveNotificationModel>,
     )
     fun onLockscreenOrShadeNotInteractive(activeNotifications: List<ActiveNotificationModel>)
-    fun onNotificationRemoved(key: String)
-    fun onNotificationUpdated(key: String)
-    fun onNotificationListUpdated(
+    fun onNotificationLocationsChanged(
         locationsProvider: Callable<Map<String, Int>>,
         notificationRanks: Map<String, Int>,
     )
@@ -41,4 +39,6 @@
         location: Int,
         isUserAction: Boolean
     )
+    fun onNotificationRemoved(key: String)
+    fun onNotificationUpdated(key: String)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerImpl.kt
index 0cb00bc..4897b42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerImpl.kt
@@ -53,10 +53,11 @@
 
     private val expansionStates: MutableMap<String, ExpansionState> =
         ConcurrentHashMap<String, ExpansionState>()
-    private val lastReportedExpansionValues: MutableMap<String, Boolean> =
+    @VisibleForTesting
+    val lastReportedExpansionValues: MutableMap<String, Boolean> =
         ConcurrentHashMap<String, Boolean>()
 
-    override fun onNotificationListUpdated(
+    override fun onNotificationLocationsChanged(
         locationsProvider: Callable<Map<String, Int>>,
         notificationRanks: Map<String, Int>,
     ) {
@@ -152,14 +153,12 @@
             )
     }
 
-    // TODO(b/308623704) wire this in with NotifPipeline updates
     override fun onNotificationRemoved(key: String) {
         // No need to track expansion states for Notifications that are removed.
         expansionStates.remove(key)
         lastReportedExpansionValues.remove(key)
     }
 
-    // TODO(b/308623704) wire this in with NotifPipeline updates
     override fun onNotificationUpdated(key: String) {
         // When the Notification is updated, we should consider it as not yet logged.
         lastReportedExpansionValues.remove(key)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStatsLoggerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStatsLoggerBinder.kt
index a05ad6e..a87c85f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStatsLoggerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStatsLoggerBinder.kt
@@ -41,6 +41,8 @@
         logger: NotificationStatsLogger,
         viewModel: NotificationLoggerViewModel,
     ) {
+        // Updates the logger about whether the Notification panel, and the individual Notifications
+        // are visible to the user.
         viewModel.isLockscreenOrShadeInteractive
             .sample(
                 combine(viewModel.isOnLockScreen, viewModel.activeNotifications, ::Pair),
@@ -52,14 +54,14 @@
                         isOnLockScreen = isOnLockScreen,
                         activeNotifications = notifications,
                     )
-                    view.onNotificationsUpdated
-                        // Delay the updates with [NOTIFICATION_UPDATES_PERIOD_MS]. If the original
+                    view.onNotificationLocationsUpdated
+                        // Delay the updates with [NOTIFICATION_UPDATE_PERIOD_MS]. If the original
                         // flow emits more than once during this period, only the latest value is
                         // emitted, meaning that we won't log the intermediate Notification states.
                         .throttle(NOTIFICATION_UPDATE_PERIOD_MS)
                         .sample(viewModel.activeNotificationRanks, ::Pair)
-                        .collect { (locationsProvider, notificationRanks) ->
-                            logger.onNotificationListUpdated(locationsProvider, notificationRanks)
+                        .collect { (locationsProvider, ranks) ->
+                            logger.onNotificationLocationsChanged(locationsProvider, ranks)
                         }
                 } else {
                     logger.onLockscreenOrShadeNotInteractive(
@@ -70,7 +72,7 @@
     }
 }
 
-private val NotificationStackScrollLayout.onNotificationsUpdated
+private val NotificationStackScrollLayout.onNotificationLocationsUpdated
     get() =
         ConflatedCallbackFlow.conflatedCallbackFlow {
             val callback =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinatorTest.kt
new file mode 100644
index 0000000..c29ff41
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinatorTest.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.statusbar.notification.collection.coordinator
+
+import android.platform.test.annotations.EnableFlags
+import android.service.notification.NotificationListenerService.REASON_CANCEL
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationStatsLogger
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import java.util.Optional
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
+class NotificationStatsLoggerCoordinatorTest : SysuiTestCase() {
+
+    private lateinit var collectionListener: NotifCollectionListener
+
+    private val pipeline: NotifPipeline = mock()
+    private val logger: NotificationStatsLogger = mock()
+    private val underTest = NotificationStatsLoggerCoordinator(Optional.of(logger))
+
+    @Before
+    fun attachPipeline() {
+        underTest.attach(pipeline)
+        collectionListener = withArgCaptor { verify(pipeline).addCollectionListener(capture()) }
+    }
+
+    @Test
+    fun onEntryAdded_loggerCalled() {
+        collectionListener.onEntryRemoved(mockEntry("key"), REASON_CANCEL)
+
+        verify(logger).onNotificationRemoved("key")
+    }
+
+    @Test
+    fun onEntryRemoved_loggerCalled() {
+        collectionListener.onEntryUpdated(mockEntry("key"))
+
+        verify(logger).onNotificationUpdated("key")
+    }
+
+    private fun mockEntry(key: String): NotificationEntry {
+        return mock { whenever(this.key).thenReturn(key) }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerTest.kt
index d7d1ffc..c61756c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerTest.kt
@@ -67,7 +67,7 @@
             // AND they're visible
             val (ranks, locations) = fakeNotificationMaps("key0", "key1")
             val callable = Callable { locations }
-            underTest.onNotificationListUpdated(callable, ranks)
+            underTest.onNotificationLocationsChanged(callable, ranks)
             runCurrent()
 
             // THEN visibility changes are reported
@@ -103,13 +103,13 @@
             // GIVEN some visible Notifications are reported
             val (ranks, locations) = fakeNotificationMaps("key0", "key1")
             val callable = Callable { locations }
-            underTest.onNotificationListUpdated(callable, ranks)
+            underTest.onNotificationLocationsChanged(callable, ranks)
             runCurrent()
             clearInvocations(mockStatusBarService, mockNotificationListenerService)
 
             // WHEN the same Notifications are removed
             val emptyCallable = Callable { emptyMap<String, Int>() }
-            underTest.onNotificationListUpdated(emptyCallable, emptyMap())
+            underTest.onNotificationLocationsChanged(emptyCallable, emptyMap())
             runCurrent()
 
             // THEN visibility changes are reported
@@ -140,13 +140,13 @@
             // GIVEN some visible Notifications are reported
             val (ranks, locations) = fakeNotificationMaps("key0", "key1")
             val callable = Callable { locations }
-            underTest.onNotificationListUpdated(callable, ranks)
+            underTest.onNotificationLocationsChanged(callable, ranks)
             runCurrent()
             clearInvocations(mockStatusBarService, mockNotificationListenerService)
 
             // WHEN the same Notifications are becoming invisible
             val emptyCallable = Callable { emptyMap<String, Int>() }
-            underTest.onNotificationListUpdated(emptyCallable, ranks)
+            underTest.onNotificationLocationsChanged(emptyCallable, ranks)
             runCurrent()
 
             // THEN visibility changes are reported
@@ -176,13 +176,13 @@
         testScope.runTest {
             // GIVEN some visible Notifications are reported
             val (ranks, locations) = fakeNotificationMaps("key0", "key1")
-            underTest.onNotificationListUpdated({ locations }, ranks)
+            underTest.onNotificationLocationsChanged({ locations }, ranks)
             runCurrent()
             clearInvocations(mockStatusBarService, mockNotificationListenerService)
 
             // WHEN the reported Notifications are changing positions
             val (newRanks, newLocations) = fakeNotificationMaps("key1", "key0")
-            underTest.onNotificationListUpdated({ newLocations }, newRanks)
+            underTest.onNotificationLocationsChanged({ newLocations }, newRanks)
             runCurrent()
 
             // THEN no visibility changes are reported
@@ -195,13 +195,13 @@
             // GIVEN some visible Notifications are reported
             val (ranks, locations) = fakeNotificationMaps("key0", "key1", "key2")
             val callable = spy(Callable { locations })
-            underTest.onNotificationListUpdated(callable, ranks)
+            underTest.onNotificationLocationsChanged(callable, ranks)
             runCurrent()
             clearInvocations(callable)
 
             // WHEN a new update comes
             val otherCallable = spy(Callable { locations })
-            underTest.onNotificationListUpdated(otherCallable, ranks)
+            underTest.onNotificationLocationsChanged(otherCallable, ranks)
             runCurrent()
 
             // THEN we call the new Callable
@@ -215,7 +215,7 @@
             // GIVEN some visible Notifications are reported
             val (ranks, locations) = fakeNotificationMaps("key0", "key1")
             val callable = Callable { locations }
-            underTest.onNotificationListUpdated(callable, ranks)
+            underTest.onNotificationLocationsChanged(callable, ranks)
             runCurrent()
             clearInvocations(mockStatusBarService, mockNotificationListenerService)
 
@@ -281,7 +281,7 @@
         }
 
     @Test
-    fun onNotificationExpanded_visibleLocation_expansionLogged() =
+    fun onNotificationExpansionChanged_whenExpandedInVisibleLocation_logsExpansion() =
         testScope.runTest {
             // WHEN a Notification is expanded
             underTest.onNotificationExpansionChanged(
@@ -303,7 +303,33 @@
         }
 
     @Test
-    fun onNotificationExpanded_notVisibleLocation_nothingLogged() =
+    fun onNotificationExpansionChanged_whenCalledTwiceWithTheSameUpdate_doesNotDuplicateLogs() =
+        testScope.runTest {
+            // GIVEN a Notification is expanded
+            underTest.onNotificationExpansionChanged(
+                key = "key",
+                isExpanded = true,
+                location = ExpandableViewState.LOCATION_MAIN_AREA,
+                isUserAction = true
+            )
+            runCurrent()
+            clearInvocations(mockStatusBarService)
+
+            // WHEN the logger receives the same expansion update
+            underTest.onNotificationExpansionChanged(
+                key = "key",
+                isExpanded = true,
+                location = ExpandableViewState.LOCATION_MAIN_AREA,
+                isUserAction = true
+            )
+            runCurrent()
+
+            // THEN the Expand event is not reported again
+            verifyZeroInteractions(mockStatusBarService)
+        }
+
+    @Test
+    fun onNotificationExpansionChanged_whenCalledForNotVisibleItem_nothingLogged() =
         testScope.runTest {
             // WHEN a NOT visible Notification is expanded
             underTest.onNotificationExpansionChanged(
@@ -319,35 +345,73 @@
         }
 
     @Test
-    fun onNotificationExpanded_notVisibleLocation_becomesVisible_expansionLogged() =
+    fun onNotificationExpansionChanged_whenNotVisibleItemBecomesVisible_logsChanges() =
         testScope.runTest {
             // WHEN a NOT visible Notification is expanded
             underTest.onNotificationExpansionChanged(
                 key = "key",
                 isExpanded = true,
                 location = ExpandableViewState.LOCATION_GONE,
-                isUserAction = true
+                isUserAction = false
             )
             runCurrent()
 
             // AND it becomes visible
             val (ranks, locations) = fakeNotificationMaps("key")
             val callable = Callable { locations }
-            underTest.onNotificationListUpdated(callable, ranks)
+            underTest.onNotificationLocationsChanged(callable, ranks)
             runCurrent()
 
             // THEN the Expand event is reported
             verify(mockStatusBarService)
                 .onNotificationExpansionChanged(
                     /* key = */ "key",
-                    /* userAction = */ true,
+                    /* userAction = */ false,
                     /* expanded = */ true,
                     NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA.ordinal
                 )
         }
 
     @Test
-    fun onNotificationCollapsed_isFirstInteraction_nothingLogged() =
+    fun onNotificationExpansionChanged_whenUpdatedItemBecomesVisible_logsChanges() =
+        testScope.runTest {
+            // GIVEN a NOT visible Notification is expanded
+            underTest.onNotificationExpansionChanged(
+                key = "key",
+                isExpanded = true,
+                location = ExpandableViewState.LOCATION_GONE,
+                isUserAction = false
+            )
+            runCurrent()
+            // AND we open the shade, so we log its events
+            val (ranks, locations) = fakeNotificationMaps("key")
+            val callable = Callable { locations }
+            underTest.onNotificationLocationsChanged(callable, ranks)
+            runCurrent()
+            // AND we close the shade, so it is NOT visible
+            val emptyCallable = Callable { emptyMap<String, Int>() }
+            underTest.onNotificationLocationsChanged(emptyCallable, ranks)
+            runCurrent()
+            clearInvocations(mockStatusBarService) // clear the previous expand log
+
+            // WHEN it receives an update
+            underTest.onNotificationUpdated("key")
+            // AND it becomes visible again
+            underTest.onNotificationLocationsChanged(callable, ranks)
+            runCurrent()
+
+            // THEN we log its expand event again
+            verify(mockStatusBarService)
+                .onNotificationExpansionChanged(
+                    /* key = */ "key",
+                    /* userAction = */ false,
+                    /* expanded = */ true,
+                    NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA.ordinal
+                )
+        }
+
+    @Test
+    fun onNotificationExpansionChanged_whenCollapsedForTheFirstTime_nothingLogged() =
         testScope.runTest {
             // WHEN a Notification is collapsed, and it is the first interaction
             underTest.onNotificationExpansionChanged(
@@ -364,7 +428,7 @@
         }
 
     @Test
-    fun onNotificationExpandedAndCollapsed_expansionChangesLogged() =
+    fun onNotificationExpansionChanged_receivesMultipleUpdates_logsChanges() =
         testScope.runTest {
             // GIVEN a Notification is expanded
             underTest.onNotificationExpansionChanged(
@@ -403,6 +467,60 @@
                 )
         }
 
+    @Test
+    fun onNotificationUpdated_clearsTrackedExpansionChanges() =
+        testScope.runTest {
+            // GIVEN some notification updates are posted
+            underTest.onNotificationExpansionChanged(
+                key = "key1",
+                isExpanded = true,
+                location = ExpandableViewState.LOCATION_MAIN_AREA,
+                isUserAction = true
+            )
+            runCurrent()
+            underTest.onNotificationExpansionChanged(
+                key = "key2",
+                isExpanded = true,
+                location = ExpandableViewState.LOCATION_MAIN_AREA,
+                isUserAction = true
+            )
+            runCurrent()
+            clearInvocations(mockStatusBarService)
+
+            // WHEN a Notification is updated
+            underTest.onNotificationUpdated("key1")
+
+            // THEN the tracked expansion changes are updated
+            assertThat(underTest.lastReportedExpansionValues.keys).containsExactly("key2")
+        }
+
+    @Test
+    fun onNotificationRemoved_clearsTrackedExpansionChanges() =
+        testScope.runTest {
+            // GIVEN some notification updates are posted
+            underTest.onNotificationExpansionChanged(
+                key = "key1",
+                isExpanded = true,
+                location = ExpandableViewState.LOCATION_MAIN_AREA,
+                isUserAction = true
+            )
+            runCurrent()
+            underTest.onNotificationExpansionChanged(
+                key = "key2",
+                isExpanded = true,
+                location = ExpandableViewState.LOCATION_MAIN_AREA,
+                isUserAction = true
+            )
+            runCurrent()
+            clearInvocations(mockStatusBarService)
+
+            // WHEN a Notification is removed
+            underTest.onNotificationRemoved("key1")
+
+            // THEN it is removed from the tracked expansion changes
+            assertThat(underTest.lastReportedExpansionValues.keys).doesNotContain("key1")
+        }
+
     private fun fakeNotificationMaps(
         vararg keys: String
     ): Pair<Map<String, Int>, Map<String, Int>> {
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 7fcef9c..3aa9cc8 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -59,6 +59,7 @@
 import android.hardware.camera2.extension.SizeList;
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
+import android.hardware.camera2.utils.SurfaceUtils;
 import android.media.Image;
 import android.media.ImageReader;
 import android.os.Binder;
@@ -1691,11 +1692,17 @@
         private final Surface mSurface;
         private final Size mSize;
         private final int mImageFormat;
+        private final int mDataspace;
 
         public OutputSurfaceImplStub(OutputSurface outputSurface) {
             mSurface = outputSurface.surface;
             mSize = new Size(outputSurface.size.width, outputSurface.size.height);
             mImageFormat = outputSurface.imageFormat;
+            if (mSurface != null) {
+                mDataspace = SurfaceUtils.getSurfaceDataspace(mSurface);
+            } else {
+                mDataspace = -1;
+            }
         }
 
         @Override
@@ -1712,6 +1719,11 @@
         public int getImageFormat() {
             return mImageFormat;
         }
+
+        @Override
+        public int getDataspace() {
+            return mDataspace;
+        }
     }
 
     private class PreviewExtenderImplStub extends IPreviewExtenderImpl.Stub implements
diff --git a/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
index ad27c52..4b85d09 100644
--- a/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
+++ b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
@@ -54,7 +54,9 @@
     void reset(@NonNull ArrayMap<String, InputMethodInfo> methodMap) {
         mSubtypeHandles.clear();
         final InputMethodSettings settings = new InputMethodSettings(methodMap, mUserId);
-        for (final InputMethodInfo imi : settings.getEnabledInputMethodListLocked()) {
+        final List<InputMethodInfo> inputMethods = settings.getEnabledInputMethodListLocked();
+        for (int i = 0; i < inputMethods.size(); ++i) {
+            final InputMethodInfo imi = inputMethods.get(i);
             if (!imi.shouldShowInInputMethodPicker()) {
                 continue;
             }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
index a763251..542165d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
@@ -46,7 +46,7 @@
     private static final String TAG = "InputMethodInfoUtils";
 
     /**
-     * Used in {@link #getFallbackLocaleForDefaultIme(ArrayList, Context)} to find the fallback IMEs
+     * Used in {@link #getFallbackLocaleForDefaultIme(List, Context)} to find the fallback IMEs
      * that are mainly used until the system becomes ready. Note that {@link Locale} in this array
      * is checked with {@link Locale#equals(Object)}, which means that {@code Locale.ENGLISH}
      * doesn't automatically match {@code Locale("en", "IN")}.
@@ -64,7 +64,7 @@
         @NonNull
         private final LinkedHashSet<InputMethodInfo> mInputMethodSet = new LinkedHashSet<>();
 
-        InputMethodListBuilder fillImes(ArrayList<InputMethodInfo> imis, Context context,
+        InputMethodListBuilder fillImes(List<InputMethodInfo> imis, Context context,
                 boolean checkDefaultAttribute, @Nullable Locale locale, boolean checkCountry,
                 String requiredSubtypeMode) {
             for (int i = 0; i < imis.size(); ++i) {
@@ -77,7 +77,7 @@
             return this;
         }
 
-        InputMethodListBuilder fillAuxiliaryImes(ArrayList<InputMethodInfo> imis, Context context) {
+        InputMethodListBuilder fillAuxiliaryImes(List<InputMethodInfo> imis, Context context) {
             // If one or more auxiliary input methods are available, OK to stop populating the list.
             for (final InputMethodInfo imi : mInputMethodSet) {
                 if (imi.isAuxiliaryIme()) {
@@ -118,7 +118,7 @@
     }
 
     private static InputMethodListBuilder getMinimumKeyboardSetWithSystemLocale(
-            ArrayList<InputMethodInfo> imis, Context context, @Nullable Locale systemLocale,
+            List<InputMethodInfo> imis, Context context, @Nullable Locale systemLocale,
             @Nullable Locale fallbackLocale) {
         // Once the system becomes ready, we pick up at least one keyboard in the following order.
         // Secondary users fall into this category in general.
@@ -167,7 +167,7 @@
     }
 
     static ArrayList<InputMethodInfo> getDefaultEnabledImes(
-            Context context, ArrayList<InputMethodInfo> imis, boolean onlyMinimum) {
+            Context context, List<InputMethodInfo> imis, boolean onlyMinimum) {
         final Locale fallbackLocale = getFallbackLocaleForDefaultIme(imis, context);
         // We will primarily rely on the system locale, but also keep relying on the fallback locale
         // as a last resort.
@@ -186,7 +186,7 @@
     }
 
     static ArrayList<InputMethodInfo> getDefaultEnabledImes(
-            Context context, ArrayList<InputMethodInfo> imis) {
+            Context context, List<InputMethodInfo> imis) {
         return getDefaultEnabledImes(context, imis, false /* onlyMinimum */);
     }
 
@@ -283,7 +283,7 @@
     }
 
     @Nullable
-    private static Locale getFallbackLocaleForDefaultIme(ArrayList<InputMethodInfo> imis,
+    private static Locale getFallbackLocaleForDefaultIme(List<InputMethodInfo> imis,
             Context context) {
         // At first, find the fallback locale from the IMEs that are declared as "default" in the
         // current locale.  Note that IME developers can declare an IME as "default" only for
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
index 4c7d755..9ddb428 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
@@ -67,7 +67,8 @@
         builder.append(ime.first);
         // Inputmethod and subtypes are saved in the settings as follows:
         // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
-        for (String subtypeId : ime.second) {
+        for (int i = 0; i < ime.second.size(); ++i) {
+            final String subtypeId = ime.second.get(i);
             builder.append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(subtypeId);
         }
     }
@@ -123,17 +124,19 @@
     }
 
     List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi) {
-        List<Pair<String, ArrayList<String>>> imsList =
+        final List<Pair<String, ArrayList<String>>> imsList =
                 getEnabledInputMethodsAndSubtypeListLocked();
-        ArrayList<InputMethodSubtype> enabledSubtypes = new ArrayList<>();
+        final List<InputMethodSubtype> enabledSubtypes = new ArrayList<>();
         if (imi != null) {
-            for (Pair<String, ArrayList<String>> imsPair : imsList) {
-                InputMethodInfo info = mMethodMap.get(imsPair.first);
+            for (int i = 0; i < imsList.size(); ++i) {
+                final Pair<String, ArrayList<String>> imsPair = imsList.get(i);
+                final InputMethodInfo info = mMethodMap.get(imsPair.first);
                 if (info != null && info.getId().equals(imi.getId())) {
                     final int subtypeCount = info.getSubtypeCount();
-                    for (int i = 0; i < subtypeCount; ++i) {
-                        InputMethodSubtype ims = info.getSubtypeAt(i);
-                        for (String s : imsPair.second) {
+                    for (int j = 0; j < subtypeCount; ++j) {
+                        final InputMethodSubtype ims = info.getSubtypeAt(j);
+                        for (int k = 0; k < imsPair.second.size(); ++k) {
+                            final String s = imsPair.second.get(k);
                             if (String.valueOf(ims.hashCode()).equals(s)) {
                                 enabledSubtypes.add(ims);
                             }
@@ -182,8 +185,9 @@
             StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
         boolean isRemoved = false;
         boolean needsAppendSeparator = false;
-        for (Pair<String, ArrayList<String>> ims : imsList) {
-            String curId = ims.first;
+        for (int i = 0; i < imsList.size(); ++i) {
+            final Pair<String, ArrayList<String>> ims = imsList.get(i);
+            final String curId = ims.first;
             if (curId.equals(id)) {
                 // We are disabling this input method, and it is
                 // currently enabled.  Skip it to remove from the
@@ -209,8 +213,9 @@
             List<Pair<String, ArrayList<String>>> imsList,
             Predicate<InputMethodInfo> matchingCondition) {
         final ArrayList<InputMethodInfo> res = new ArrayList<>();
-        for (Pair<String, ArrayList<String>> ims : imsList) {
-            InputMethodInfo info = mMethodMap.get(ims.first);
+        for (int i = 0; i < imsList.size(); ++i) {
+            final Pair<String, ArrayList<String>> ims = imsList.get(i);
+            final InputMethodInfo info = mMethodMap.get(ims.first);
             if (info != null && !info.isVrOnly()
                     && (matchingCondition == null || matchingCondition.test(info))) {
                 res.add(info);
@@ -239,15 +244,16 @@
 
     private void saveSubtypeHistory(
             List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
-        StringBuilder builder = new StringBuilder();
+        final StringBuilder builder = new StringBuilder();
         boolean isImeAdded = false;
         if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
             builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(
                     newSubtypeId);
             isImeAdded = true;
         }
-        for (Pair<String, String> ime : savedImes) {
-            String imeId = ime.first;
+        for (int i = 0; i < savedImes.size(); ++i) {
+            final Pair<String, String> ime = savedImes.get(i);
+            final String imeId = ime.first;
             String subtypeId = ime.second;
             if (TextUtils.isEmpty(subtypeId)) {
                 subtypeId = NOT_A_SUBTYPE_ID_STR;
@@ -265,8 +271,9 @@
     }
 
     private void addSubtypeToHistory(String imeId, String subtypeId) {
-        List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
-        for (Pair<String, String> ime : subtypeHistory) {
+        final List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
+        for (int i = 0; i < subtypeHistory.size(); ++i) {
+            final Pair<String, String> ime = subtypeHistory.get(i);
             if (ime.first.equals(imeId)) {
                 if (DEBUG) {
                     Slog.v(TAG, "Subtype found in the history: " + imeId + ", "
@@ -334,10 +341,11 @@
     }
 
     private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
-        List<Pair<String, ArrayList<String>>> enabledImes =
+        final List<Pair<String, ArrayList<String>>> enabledImes =
                 getEnabledInputMethodsAndSubtypeListLocked();
-        List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
-        for (Pair<String, String> imeAndSubtype : subtypeHistory) {
+        final List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
+        for (int i = 0; i < subtypeHistory.size(); ++i) {
+            final Pair<String, String> imeAndSubtype = subtypeHistory.get(i);
             final String imeInTheHistory = imeAndSubtype.first;
             // If imeId is empty, returns the first IME and subtype in the history
             if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
@@ -363,11 +371,12 @@
     private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
             ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
         final LocaleList localeList = SystemLocaleWrapper.get(mCurrentUserId);
-        for (Pair<String, ArrayList<String>> enabledIme : enabledImes) {
+        for (int i = 0; i < enabledImes.size(); ++i) {
+            final Pair<String, ArrayList<String>> enabledIme = enabledImes.get(i);
             if (enabledIme.first.equals(imeId)) {
                 final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
                 final InputMethodInfo imi = mMethodMap.get(imeId);
-                if (explicitlyEnabledSubtypes.size() == 0) {
+                if (explicitlyEnabledSubtypes.isEmpty()) {
                     // If there are no explicitly enabled subtypes, applicable subtypes are
                     // enabled implicitly.
                     // If IME is enabled and no subtypes are enabled, applicable subtypes
@@ -377,15 +386,16 @@
                                 SubtypeUtils.getImplicitlyApplicableSubtypesLocked(localeList,
                                         imi);
                         final int numSubtypes = implicitlyEnabledSubtypes.size();
-                        for (int i = 0; i < numSubtypes; ++i) {
-                            final InputMethodSubtype st = implicitlyEnabledSubtypes.get(i);
+                        for (int j = 0; j < numSubtypes; ++j) {
+                            final InputMethodSubtype st = implicitlyEnabledSubtypes.get(j);
                             if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
                                 return subtypeHashCode;
                             }
                         }
                     }
                 } else {
-                    for (String s : explicitlyEnabledSubtypes) {
+                    for (int j = 0; j < explicitlyEnabledSubtypes.size(); ++j) {
+                        final String s = explicitlyEnabledSubtypes.get(j);
                         if (s.equals(subtypeHashCode)) {
                             // If both imeId and subtypeId are enabled, return subtypeId.
                             try {