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
This commit is contained in:
Sunny Goyal
2025-01-29 14:25:44 -08:00
committed by Android Build Coastguard Worker
parent 913244fac3
commit a315410ea3
3 changed files with 54 additions and 15 deletions
@@ -177,12 +177,16 @@ public class FolderIcon extends FrameLayout implements FolderListener, FloatingI
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 @@ public class FolderIcon extends FrameLayout implements FolderListener, FloatingI
icon.mPreviewVerifier.setFolderInfo(folderInfo);
icon.updatePreviewItems(false);
folderInfo.addListener(icon);
return icon;
}
@@ -51,11 +51,12 @@ import com.android.launcher3.util.Preconditions;
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 @@ public class GridCustomizationsProvider extends ContentProvider {
// 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 @@ public class GridCustomizationsProvider extends ContentProvider {
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 @@ public class GridCustomizationsProvider extends ContentProvider {
&& 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);
}
}
}
}
@@ -105,7 +105,6 @@ public class PreviewSurfaceRenderer {
private final SurfaceControlViewHost mSurfaceControlViewHost;
private boolean mDestroyed = false;
private LauncherPreviewRenderer mRenderer;
private boolean mHideQsb;
@Nullable private FrameLayout mViewRoot = null;
@@ -224,9 +223,8 @@ public class PreviewSurfaceRenderer {
* @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 @@ public class PreviewSurfaceRenderer {
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());