Fixing leaks in LauncherPreview

> Do not add listeners when binding FolderIcon for preview
> Cleaning up preview object when the caller is no longer holding
  on to the communication channel for preview.

Bug: 393086035
Flag: EXEMPT bugfix
Test: Verified manually
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:986d7cb9c09a22915c922cf8e72c03ce36883742)
Merged-In: I4b758e6ce103c5201ef05ab824dd4e02f98c40b6
Change-Id: I4b758e6ce103c5201ef05ab824dd4e02f98c40b6
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index de1bcc3..9ff6475 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -177,12 +177,16 @@
         FolderIcon icon = inflateIcon(resId, activityContext, group, folderInfo);
         folder.setFolderIcon(icon);
         folder.bind(folderInfo);
+
         icon.setFolder(folder);
+        folderInfo.addListener(icon);
         return icon;
     }
 
     /**
-     * Builds a FolderIcon to be added to the Launcher
+     * Builds a FolderIcon to be added to the activity.
+     * This method doesn't add any listeners to the FolderInfo, and hence any changes to the info
+     * will not be reflected in the folder.
      */
     public static FolderIcon inflateIcon(int resId, ActivityContext activity,
             @Nullable ViewGroup group, FolderInfo folderInfo) {
@@ -228,8 +232,6 @@
         icon.mPreviewVerifier.setFolderInfo(folderInfo);
         icon.updatePreviewItems(false);
 
-        folderInfo.addListener(icon);
-
         return icon;
     }
 
diff --git a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
index 7367f2e..836ae98 100644
--- a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
+++ b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
@@ -51,11 +51,12 @@
 import com.android.launcher3.util.RunnableList;
 import com.android.systemui.shared.Flags;
 
+import java.lang.ref.WeakReference;
 import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
-import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 
 /**
@@ -121,7 +122,7 @@
 
     // Set of all active previews used to track duplicate memory allocations
     private final Set<PreviewLifecycleObserver> mActivePreviews =
-            Collections.newSetFromMap(new WeakHashMap<>());
+            Collections.newSetFromMap(new ConcurrentHashMap<>());
 
     @Override
     public boolean onCreate() {
@@ -317,8 +318,15 @@
             Bundle result = new Bundle();
             result.putParcelable(KEY_SURFACE_PACKAGE, renderer.getSurfacePackage());
 
-            Messenger messenger =
-                    new Messenger(new Handler(UI_HELPER_EXECUTOR.getLooper(), observer));
+            mActivePreviews.add(observer);
+            lifeCycleTracker.add(() -> mActivePreviews.remove(observer));
+
+            // Wrap the callback in a weak reference. This ensures that the callback is not kept
+            // alive due to the Messenger's IBinder
+            Messenger messenger = new Messenger(new Handler(
+                    UI_HELPER_EXECUTOR.getLooper(),
+                    new WeakCallbackWrapper(observer)));
+
             Message msg = Message.obtain();
             msg.replyTo = messenger;
             result.putParcelable(KEY_CALLBACK, msg);
@@ -400,4 +408,34 @@
                     && plo.renderer.getDisplayId() == renderer.getDisplayId();
         }
     }
+
+    /**
+     * A WeakReference wrapper around Handler.Callback to avoid passing hard-reference over IPC
+     * when using a Messenger
+     */
+    private static class WeakCallbackWrapper implements Handler.Callback {
+
+        private final WeakReference<Handler.Callback> mActual;
+        private final Message mCleanupMessage;
+
+        WeakCallbackWrapper(Handler.Callback actual) {
+            mActual = new WeakReference<>(actual);
+            mCleanupMessage = new Message();
+        }
+
+        @Override
+        public boolean handleMessage(Message message) {
+            Handler.Callback actual = mActual.get();
+            return actual != null && actual.handleMessage(message);
+        }
+
+        @Override
+        protected void finalize() throws Throwable {
+            super.finalize();
+            Handler.Callback actual = mActual.get();
+            if (actual != null) {
+                actual.handleMessage(mCleanupMessage);
+            }
+        }
+    }
 }
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index 3000b25..a7cf1a7 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -105,7 +105,6 @@
     private final SurfaceControlViewHost mSurfaceControlViewHost;
 
     private boolean mDestroyed = false;
-    private LauncherPreviewRenderer mRenderer;
     private boolean mHideQsb;
     @Nullable private FrameLayout mViewRoot = null;
 
@@ -224,9 +223,8 @@
      * @param hide True to hide and false to show.
      */
     public void hideBottomRow(boolean hide) {
-        if (mRenderer != null) {
-            mRenderer.hideBottomRow(hide);
-        }
+        mHideQsb = hide;
+        loadAsync();
     }
 
     /**
@@ -368,15 +366,16 @@
         if (mDestroyed) {
             return;
         }
+        LauncherPreviewRenderer renderer;
         if (Flags.newCustomizationPickerUi()) {
-            mRenderer = new LauncherPreviewRenderer(inflationContext, idp, mPreviewColorOverride,
+            renderer = new LauncherPreviewRenderer(inflationContext, idp, mPreviewColorOverride,
                     mWallpaperColors, launcherWidgetSpanInfo);
         } else {
-            mRenderer = new LauncherPreviewRenderer(inflationContext, idp,
+            renderer = new LauncherPreviewRenderer(inflationContext, idp,
                     mWallpaperColors, launcherWidgetSpanInfo);
         }
-        mRenderer.hideBottomRow(mHideQsb);
-        View view = mRenderer.getRenderedView(dataModel, widgetProviderInfoMap);
+        renderer.hideBottomRow(mHideQsb);
+        View view = renderer.getRenderedView(dataModel, widgetProviderInfoMap);
         // This aspect scales the view to fit in the surface and centers it
         final float scale = Math.min(mWidth / (float) view.getMeasuredWidth(),
                 mHeight / (float) view.getMeasuredHeight());