From e0553d7da30fb7e7d6e488bc344bedc1d63b9a06 Mon Sep 17 00:00:00 2001 From: Fengjiang Li Date: Thu, 20 Feb 2025 21:55:05 -0800 Subject: [PATCH] [Memory Leak] Fix leak of LauncherAppWidgetHostView via StateManager.StateListener Rather than register a StateListener for each LauncherAppWidgetHostView, just register one StateListener which, upon launcher state is changed to NORMAL, iterate over all child views under Workspace and perform a11y action if view tag is set. Fix: 397978703 Test: presubmit Flag: EXEMPT bug fix Change-Id: I044d012b74eaa5356196c5503318fa8ab389ec46 --- res/values/id.xml | 1 + src/com/android/launcher3/CellLayout.java | 16 ++------- src/com/android/launcher3/Workspace.java | 44 +++++++++++++++++------ 3 files changed, 37 insertions(+), 24 deletions(-) diff --git a/res/values/id.xml b/res/values/id.xml index 67692d8fee..78b8308cc9 100644 --- a/res/values/id.xml +++ b/res/values/id.xml @@ -79,6 +79,7 @@ + diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 257f911852..0ce966b99b 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -16,7 +16,6 @@ package com.android.launcher3; -import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.dragndrop.DraggableView.DRAGGABLE_ICON; import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR; import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_REORDER_PREVIEW_OFFSET; @@ -71,7 +70,6 @@ import com.android.launcher3.dragndrop.DraggableView; import com.android.launcher3.folder.PreviewBackground; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.LauncherAppWidgetInfo; -import com.android.launcher3.statemanager.StateManager; import com.android.launcher3.util.CellAndSpan; import com.android.launcher3.util.GridOccupancy; import com.android.launcher3.util.MSDLPlayerWrapper; @@ -791,18 +789,8 @@ public class CellLayout extends ViewGroup { // Whenever an app is added, if Accessibility service is enabled, focus on that app. if (mActivity instanceof Launcher) { - Launcher.cast(mActivity).getStateManager().addStateListener( - new StateManager.StateListener() { - @Override - public void onStateTransitionComplete(LauncherState finalState) { - if (finalState == NORMAL) { - child.performAccessibilityAction( - AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null); - Launcher.cast(mActivity).getStateManager() - .removeStateListener(this); - } - } - }); + child.setTag(R.id.perform_a11y_action_on_launcher_state_normal_tag, + AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS); } if (markCells) markCellsAsOccupiedForView(child); diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index cfb116147c..94ff44145b 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -53,6 +53,7 @@ import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.os.Bundle; import android.os.Parcelable; import android.util.AttributeSet; import android.util.Log; @@ -299,6 +300,17 @@ public class Workspace extends PagedView private final StatsLogManager mStatsLogManager; private final MSDLPlayerWrapper mMSDLPlayerWrapper; + + private final StateManager.StateListener mAccessibilityDropListener = + new StateListener<>() { + @Override + public void onStateTransitionComplete(LauncherState finalState) { + if (finalState == NORMAL) { + performAccessibilityActionOnViewTree(Workspace.this); + } + } + }; + @Nullable private DragController.DragListener mAccessibilityDragListener; @@ -1454,11 +1466,13 @@ public class Workspace extends PagedView super.onAttachedToWindow(); mWallpaperOffset.setWindowToken(getWindowToken()); computeScroll(); + mLauncher.getStateManager().addStateListener(mAccessibilityDropListener); } protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mWallpaperOffset.setWindowToken(null); + mLauncher.getStateManager().removeStateListener(mAccessibilityDropListener); } @Override @@ -2239,16 +2253,8 @@ public class Workspace extends PagedView // the order of operations in this method related to the StateListener below, please // test that accessibility moves retain focus after accessibility dropping an item. // Accessibility focus must be requested after launcher is back to a normal state - mLauncher.getStateManager().addStateListener(new StateListener() { - @Override - public void onStateTransitionComplete(LauncherState finalState) { - if (finalState == NORMAL) { - cell.performAccessibilityAction( - AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null); - mLauncher.getStateManager().removeStateListener(this); - } - } - }); + cell.setTag(R.id.perform_a11y_action_on_launcher_state_normal_tag, + AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS); } } @@ -3580,4 +3586,22 @@ public class Workspace extends PagedView onEndStateTransition(); } } + + /** + * Recursively check view tag {@link R.id.perform_a11y_action_on_launcher_state_normal_tag} and + * call {@link View#performAccessibilityAction(int, Bundle)} on view tree. The tag is cleared + * after this call. + */ + private static void performAccessibilityActionOnViewTree(View view) { + Object tag = view.getTag(R.id.perform_a11y_action_on_launcher_state_normal_tag); + if (tag instanceof Integer) { + view.performAccessibilityAction((int) tag, null); + view.setTag(R.id.perform_a11y_action_on_launcher_state_normal_tag, null); + } + if (view instanceof ViewGroup viewgroup) { + for (int i = 0; i < viewgroup.getChildCount(); i++) { + performAccessibilityActionOnViewTree(viewgroup.getChildAt(i)); + } + } + } }