Fixing LauncherIcons leaking outside sandbox context

Since LauncherIcons was using a global static pool, a custom
instance for a test could leak into the global pool, affecting
other tests

Bug: 335280439
Test: Verified image test on device
Flag: None
Change-Id: Iedd19c8e69c928e44b65eae7eba0167b03b5df6b
This commit is contained in:
Sunny Goyal
2024-05-07 10:19:57 -07:00
parent bc549958d9
commit bf3efe8af0
3 changed files with 45 additions and 76 deletions
@@ -206,7 +206,7 @@ public class LauncherAppState implements SafeCloseable {
}
private void refreshAndReloadLauncher() {
LauncherIcons.clearPool();
LauncherIcons.clearPool(mContext);
mIconCache.updateIconParams(
mInvariantDeviceProfile.fillResIconDpi, mInvariantDeviceProfile.iconBitmapSize);
mModel.forceReload();
@@ -76,7 +76,6 @@ import com.android.launcher3.celllayout.CellLayoutLayoutParams;
import com.android.launcher3.celllayout.CellPosMapper;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.WidgetItem;
@@ -107,7 +106,6 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* Utility class for generating the preview of Launcher for a given InvariantDeviceProfile.
@@ -126,44 +124,12 @@ public class LauncherPreviewRenderer extends ContextWrapper
*/
public static class PreviewContext extends SandboxContext {
private final InvariantDeviceProfile mIdp;
private final ConcurrentLinkedQueue<LauncherIconsForPreview> mIconPool =
new ConcurrentLinkedQueue<>();
public PreviewContext(Context base, InvariantDeviceProfile idp) {
super(base);
mIdp = idp;
putObject(InvariantDeviceProfile.INSTANCE, idp);
putObject(LauncherAppState.INSTANCE,
new LauncherAppState(this, null /* iconCacheFileName */));
}
/**
* Creates a new LauncherIcons for the preview, skipping the global pool
*/
public LauncherIcons newLauncherIcons(Context context) {
LauncherIconsForPreview launcherIconsForPreview = mIconPool.poll();
if (launcherIconsForPreview != null) {
return launcherIconsForPreview;
}
return new LauncherIconsForPreview(context, mIdp.fillResIconDpi, mIdp.iconBitmapSize,
-1 /* poolId */);
}
private final class LauncherIconsForPreview extends LauncherIcons {
private LauncherIconsForPreview(Context context, int fillResIconDpi, int iconBitmapSize,
int poolId) {
super(context, fillResIconDpi, iconBitmapSize, poolId);
}
@Override
public void recycle() {
// Clear any temporary state variables
clear();
mIconPool.offer(this);
}
}
}
private final List<OnDeviceProfileChangeListener> mDpChangeListeners = new ArrayList<>();
@@ -25,79 +25,53 @@ import androidx.annotation.NonNull;
import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.graphics.LauncherPreviewRenderer;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.UserIconInfo;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class
* that are threadsafe.
*/
public class LauncherIcons extends BaseIconFactory implements AutoCloseable {
private static final Object sPoolSync = new Object();
private static LauncherIcons sPool;
private static int sPoolId = 0;
private static final MainThreadInitializedObject<Pool> POOL =
new MainThreadInitializedObject<>(Pool::new);
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
public static LauncherIcons obtain(Context context) {
if (context instanceof LauncherPreviewRenderer.PreviewContext) {
return ((LauncherPreviewRenderer.PreviewContext) context).newLauncherIcons(context);
}
int poolId;
synchronized (sPoolSync) {
if (sPool != null) {
LauncherIcons m = sPool;
sPool = m.next;
m.next = null;
return m;
}
poolId = sPoolId;
}
InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(context);
return new LauncherIcons(context, idp.fillResIconDpi, idp.iconBitmapSize, poolId);
return POOL.get(context).obtain();
}
public static void clearPool() {
synchronized (sPoolSync) {
sPool = null;
sPoolId++;
}
public static void clearPool(Context context) {
POOL.get(context).close();
}
private final int mPoolId;
private LauncherIcons next;
private final ConcurrentLinkedQueue<LauncherIcons> mPool;
private MonochromeIconFactory mMonochromeIconFactory;
protected LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize, int poolId) {
protected LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize,
ConcurrentLinkedQueue<LauncherIcons> pool) {
super(context, fillResIconDpi, iconBitmapSize,
IconShape.INSTANCE.get(context).getShape().enableShapeDetection());
mMonoIconEnabled = Themes.isThemedIconEnabled(context);
mPoolId = poolId;
mPool = pool;
}
/**
* Recycles a LauncherIcons that may be in-use.
*/
public void recycle() {
synchronized (sPoolSync) {
if (sPoolId != mPoolId) {
return;
}
// Clear any temporary state variables
clear();
next = sPool;
sPool = this;
}
clear();
mPool.add(this);
}
@Override
@@ -122,4 +96,33 @@ public class LauncherIcons extends BaseIconFactory implements AutoCloseable {
public void close() {
recycle();
}
private static class Pool implements SafeCloseable {
private final Context mContext;
@NonNull
private ConcurrentLinkedQueue<LauncherIcons> mPool = new ConcurrentLinkedQueue<>();
private Pool(Context context) {
mContext = context;
}
public LauncherIcons obtain() {
ConcurrentLinkedQueue<LauncherIcons> pool = mPool;
LauncherIcons m = pool.poll();
if (m == null) {
InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mContext);
return new LauncherIcons(mContext, idp.fillResIconDpi, idp.iconBitmapSize, pool);
} else {
return m;
}
}
@Override
public void close() {
mPool = new ConcurrentLinkedQueue<>();
}
}
}