summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Fengjiang Li <fengjial@google.com> 2025-04-02 17:04:37 -0700
committer Android Build Coastguard Worker <android-build-coastguard-worker@google.com> 2025-04-04 21:39:56 -0700
commita88c057874b81a4bbdd72e27cd8cfaf2863afb98 (patch)
tree0a0cdd5025ecdbcdf9f5b657ab43ef579cad4982
parent03dd831d1f9edcfe56a60b200036e7ebfe1b8bef (diff)
[Memory Leak] Fix leak of Launcher activity from recent view's input consumer
Ensure we always clear the consumer, which has strong ref to RecentsView and launcher activity, once gesture is ended Fix: 407823225 Test: presubmit Flag: NONE - released code (cherry picked from https://googleplex-android-review.googlesource.com/q/commit:222fb581bda7f16b8f44c41aae489a025507ef51) Merged-In: I90f2212247965697a4077890404bb577943efa37 Change-Id: I90f2212247965697a4077890404bb577943efa37
-rw-r--r--quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java4
-rw-r--r--quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java2
-rw-r--r--quickstep/src/com/android/quickstep/util/CachedEventDispatcher.java5
-rw-r--r--quickstep/tests/multivalentTests/src/com/android/quickstep/util/CachedEventDispatcherTest.kt106
4 files changed, 117 insertions, 0 deletions
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index f803709a46..8eb9c754f2 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -2348,6 +2348,10 @@ public abstract class AbsSwipeUpHandler<
}
}
+ /**
+ * The returned Consumer has strong ref to RecentsView and thus Launcher activity. Caller should
+ * ensure it clears the ref to returned consumer once gesture is ended.
+ */
public Consumer<MotionEvent> getRecentsViewDispatcher(float navbarRotation) {
return mRecentsView != null ? mRecentsView.getEventDispatcher(navbarRotation) : null;
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 54cc391293..5a1e5b5330 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -521,6 +521,8 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
mVelocityTracker = null;
}
mMotionPauseDetector.clear();
+ // Clear ref to recents view and launcher activity on action up or cancel to avoid leak
+ mRecentsViewDispatcher.clearConsumer();
}
@Override
diff --git a/quickstep/src/com/android/quickstep/util/CachedEventDispatcher.java b/quickstep/src/com/android/quickstep/util/CachedEventDispatcher.java
index 194c7d4f41..6b00a1d5c1 100644
--- a/quickstep/src/com/android/quickstep/util/CachedEventDispatcher.java
+++ b/quickstep/src/com/android/quickstep/util/CachedEventDispatcher.java
@@ -63,6 +63,11 @@ public class CachedEventDispatcher {
mLastEvent = null;
}
+ /** Clear the consumer. */
+ public void clearConsumer() {
+ mConsumer = null;
+ }
+
public boolean hasConsumer() {
return mConsumer != null;
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/CachedEventDispatcherTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/CachedEventDispatcherTest.kt
new file mode 100644
index 0000000000..296171b866
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/CachedEventDispatcherTest.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util
+
+import android.os.SystemClock
+import android.view.MotionEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import java.util.function.Consumer
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidJUnit4::class)
+class CachedEventDispatcherTest {
+
+ private lateinit var underTest: CachedEventDispatcher
+
+ @Mock private lateinit var consumer: Consumer<MotionEvent>
+ @Captor private lateinit var motionEventCaptor: ArgumentCaptor<MotionEvent>
+
+ private lateinit var motionEvent: MotionEvent
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ motionEvent =
+ MotionEvent.obtain(
+ 0L,
+ SystemClock.elapsedRealtime(),
+ MotionEvent.ACTION_DOWN,
+ 0f,
+ 0f,
+ 0,
+ )
+ underTest = CachedEventDispatcher()
+ }
+
+ @Test
+ fun testInitialState() {
+ assertFalse(underTest.hasConsumer())
+ }
+
+ @Test
+ fun dispatchMotionEvents() {
+ underTest.setConsumer(consumer)
+
+ underTest.dispatchEvent(motionEvent)
+
+ assertTrue(underTest.hasConsumer())
+ verify(consumer).accept(motionEventCaptor.capture())
+ assertTrue(isMotionEventSame(motionEventCaptor.value, motionEvent))
+ }
+
+ @Test
+ fun dispatchMotionEvents_after_settingConsumer() {
+ underTest.dispatchEvent(motionEvent)
+
+ underTest.setConsumer(consumer)
+
+ verify(consumer).accept(motionEventCaptor.capture())
+ assertTrue(isMotionEventSame(motionEventCaptor.value, motionEvent))
+ }
+
+ @Test
+ fun clearConsumer_notDispatchToConsumer() {
+ underTest.setConsumer(consumer)
+ underTest.dispatchEvent(motionEvent)
+ reset(consumer)
+
+ underTest.clearConsumer()
+
+ assertFalse(underTest.hasConsumer())
+ underTest.dispatchEvent(motionEvent)
+ verifyNoMoreInteractions(consumer)
+ }
+
+ private fun isMotionEventSame(e1: MotionEvent, e2: MotionEvent): Boolean {
+ return e1.action == e2.action &&
+ e1.eventTime == e2.eventTime &&
+ e1.x == e2.x &&
+ e1.y == e2.y
+ }
+}