Fix ImageWallpaper memory regression
Scale bitmap to fit display size leads to memory regression, this cl removes the sacling logic and also disable wallpaper transitions when the bitmap size is smaller than display size to avoid broken visual. Bug: 147379974 Bug: 145897588 Bug: 124838911 Test: Manually Test: atest com.android.systemui.ImageWallpaperTest --rerun-until-failure 20 Test: atest com.android.systemui Change-Id: I243274af54538fc89268c448aa2c5a95f63c7ae3
This commit is contained in:
@@ -18,12 +18,14 @@ package com.android.systemui;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Rect;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Trace;
|
||||
import android.service.wallpaper.WallpaperService;
|
||||
import android.util.Log;
|
||||
import android.util.Size;
|
||||
import android.view.DisplayInfo;
|
||||
import android.view.SurfaceHolder;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
@@ -93,14 +95,20 @@ public class ImageWallpaper extends WallpaperService {
|
||||
private StatusBarStateController mController;
|
||||
private final Runnable mFinishRenderingTask = this::finishRendering;
|
||||
private final boolean mNeedTransition;
|
||||
private boolean mShouldStopTransition;
|
||||
@VisibleForTesting
|
||||
final boolean mIsHighEndGfx;
|
||||
private final boolean mDisplayNeedsBlanking;
|
||||
private final DisplayInfo mDisplayInfo = new DisplayInfo();
|
||||
private final Object mMonitor = new Object();
|
||||
private boolean mNeedRedraw;
|
||||
// This variable can only be accessed in synchronized block.
|
||||
private boolean mWaitingForRendering;
|
||||
|
||||
GLEngine(Context context, DozeParameters dozeParameters) {
|
||||
mNeedTransition = ActivityManager.isHighEndGfx()
|
||||
&& !dozeParameters.getDisplayNeedsBlanking();
|
||||
mIsHighEndGfx = ActivityManager.isHighEndGfx();
|
||||
mDisplayNeedsBlanking = dozeParameters.getDisplayNeedsBlanking();
|
||||
mNeedTransition = mIsHighEndGfx && !mDisplayNeedsBlanking;
|
||||
|
||||
// We will preserve EGL context when we are in lock screen or aod
|
||||
// to avoid janking in following transition, we need to release when back to home.
|
||||
@@ -112,14 +120,23 @@ public class ImageWallpaper extends WallpaperService {
|
||||
|
||||
@Override
|
||||
public void onCreate(SurfaceHolder surfaceHolder) {
|
||||
mEglHelper = new EglHelper();
|
||||
mEglHelper = getEglHelperInstance();
|
||||
// Deferred init renderer because we need to get wallpaper by display context.
|
||||
mRenderer = new ImageWallpaperRenderer(getDisplayContext(), this /* SurfaceProxy */);
|
||||
mRenderer = getRendererInstance();
|
||||
getDisplayContext().getDisplay().getDisplayInfo(mDisplayInfo);
|
||||
setFixedSizeAllowed(true);
|
||||
setOffsetNotificationsEnabled(true);
|
||||
updateSurfaceSize();
|
||||
}
|
||||
|
||||
EglHelper getEglHelperInstance() {
|
||||
return new EglHelper();
|
||||
}
|
||||
|
||||
ImageWallpaperRenderer getRendererInstance() {
|
||||
return new ImageWallpaperRenderer(getDisplayContext(), this /* SurfaceProxy */);
|
||||
}
|
||||
|
||||
private void updateSurfaceSize() {
|
||||
SurfaceHolder holder = getSurfaceHolder();
|
||||
Size frameSize = mRenderer.reportSurfaceSize();
|
||||
@@ -128,6 +145,26 @@ public class ImageWallpaper extends WallpaperService {
|
||||
holder.setFixedSize(width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if necessary to stop transition with current wallpaper on this device. <br/>
|
||||
* This should only be invoked after {@link #onSurfaceCreated(SurfaceHolder)}}
|
||||
* is invoked since it needs display context and surface frame size.
|
||||
* @return true if need to stop transition.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
boolean checkIfShouldStopTransition() {
|
||||
int orientation = getDisplayContext().getResources().getConfiguration().orientation;
|
||||
Rect frame = getSurfaceHolder().getSurfaceFrame();
|
||||
Rect display = new Rect();
|
||||
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
display.set(0, 0, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
|
||||
} else {
|
||||
display.set(0, 0, mDisplayInfo.logicalHeight, mDisplayInfo.logicalWidth);
|
||||
}
|
||||
return mNeedTransition
|
||||
&& (frame.width() < display.width() || frame.height() < display.height());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOffsetsChanged(float xOffset, float yOffset, float xOffsetStep,
|
||||
float yOffsetStep, int xPixelOffset, int yPixelOffset) {
|
||||
@@ -137,12 +174,14 @@ public class ImageWallpaper extends WallpaperService {
|
||||
@Override
|
||||
public void onAmbientModeChanged(boolean inAmbientMode, long animationDuration) {
|
||||
if (!mNeedTransition) return;
|
||||
final long duration = mShouldStopTransition ? 0 : animationDuration;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onAmbientModeChanged: inAmbient=" + inAmbientMode
|
||||
+ ", duration=" + animationDuration);
|
||||
+ ", duration=" + duration
|
||||
+ ", mShouldStopTransition=" + mShouldStopTransition);
|
||||
}
|
||||
mWorker.getThreadHandler().post(
|
||||
() -> mRenderer.updateAmbientMode(inAmbientMode, animationDuration));
|
||||
() -> mRenderer.updateAmbientMode(inAmbientMode, duration));
|
||||
if (inAmbientMode && animationDuration == 0) {
|
||||
// This means that we are transiting from home to aod, to avoid
|
||||
// race condition between window visibility and transition,
|
||||
@@ -183,6 +222,7 @@ public class ImageWallpaper extends WallpaperService {
|
||||
|
||||
@Override
|
||||
public void onSurfaceCreated(SurfaceHolder holder) {
|
||||
mShouldStopTransition = checkIfShouldStopTransition();
|
||||
mWorker.getThreadHandler().post(() -> {
|
||||
mEglHelper.init(holder, needSupportWideColorGamut());
|
||||
mRenderer.onSurfaceCreated();
|
||||
@@ -348,15 +388,13 @@ public class ImageWallpaper extends WallpaperService {
|
||||
protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
|
||||
super.dump(prefix, fd, out, args);
|
||||
out.print(prefix); out.print("Engine="); out.println(this);
|
||||
|
||||
boolean isHighEndGfx = ActivityManager.isHighEndGfx();
|
||||
out.print(prefix); out.print("isHighEndGfx="); out.println(isHighEndGfx);
|
||||
|
||||
out.print(prefix); out.print("isHighEndGfx="); out.println(mIsHighEndGfx);
|
||||
out.print(prefix); out.print("displayNeedsBlanking=");
|
||||
out.println(
|
||||
mDozeParameters != null ? mDozeParameters.getDisplayNeedsBlanking() : "null");
|
||||
|
||||
out.println(mDisplayNeedsBlanking);
|
||||
out.print(prefix); out.print("displayInfo="); out.print(mDisplayInfo);
|
||||
out.print(prefix); out.print("mNeedTransition="); out.println(mNeedTransition);
|
||||
out.print(prefix); out.print("mShouldStopTransition=");
|
||||
out.println(mShouldStopTransition);
|
||||
out.print(prefix); out.print("StatusBarState=");
|
||||
out.println(mController != null ? mController.getState() : "null");
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ import android.util.Log;
|
||||
import android.util.MathUtils;
|
||||
import android.util.Size;
|
||||
import android.view.DisplayInfo;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.android.systemui.R;
|
||||
|
||||
@@ -71,8 +70,7 @@ public class ImageWallpaperRenderer implements GLWallpaperRenderer,
|
||||
}
|
||||
|
||||
DisplayInfo displayInfo = new DisplayInfo();
|
||||
WindowManager wm = context.getSystemService(WindowManager.class);
|
||||
wm.getDefaultDisplay().getDisplayInfo(displayInfo);
|
||||
context.getDisplay().getDisplayInfo(displayInfo);
|
||||
|
||||
// We only do transition in portrait currently, b/137962047.
|
||||
int orientation = context.getResources().getConfiguration().orientation;
|
||||
@@ -88,6 +86,10 @@ public class ImageWallpaperRenderer implements GLWallpaperRenderer,
|
||||
mImageProcessHelper = new ImageProcessHelper();
|
||||
mImageRevealHelper = new ImageRevealHelper(this);
|
||||
|
||||
startProcessingImage();
|
||||
}
|
||||
|
||||
protected void startProcessingImage() {
|
||||
if (loadBitmap()) {
|
||||
// Compute threshold of the image, this is an async work.
|
||||
mImageProcessHelper.start(mBitmap);
|
||||
@@ -113,7 +115,7 @@ public class ImageWallpaperRenderer implements GLWallpaperRenderer,
|
||||
mBitmap = null;
|
||||
}
|
||||
|
||||
private boolean loadBitmap() {
|
||||
protected boolean loadBitmap() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "loadBitmap: mBitmap=" + mBitmap);
|
||||
}
|
||||
@@ -122,12 +124,7 @@ public class ImageWallpaperRenderer implements GLWallpaperRenderer,
|
||||
mWcgContent = mWallpaperManager.wallpaperSupportsWcg(WallpaperManager.FLAG_SYSTEM);
|
||||
mWallpaperManager.forgetLoadedWallpaper();
|
||||
if (mBitmap != null) {
|
||||
float scale = (float) mScissor.height() / mBitmap.getHeight();
|
||||
int surfaceHeight = Math.max(mScissor.height(), mBitmap.getHeight());
|
||||
int surfaceWidth = scale > 1f
|
||||
? Math.round(mBitmap.getWidth() * scale)
|
||||
: mBitmap.getWidth();
|
||||
mSurfaceSize.set(0, 0, surfaceWidth, surfaceHeight);
|
||||
mSurfaceSize.set(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
|
||||
}
|
||||
}
|
||||
if (DEBUG) {
|
||||
|
||||
@@ -16,35 +16,185 @@
|
||||
|
||||
package com.android.systemui;
|
||||
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.common.truth.Truth.assertWithMessage;
|
||||
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.WallpaperManager;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.ColorSpace;
|
||||
import android.graphics.Rect;
|
||||
import android.hardware.display.DisplayManagerGlobal;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
import android.util.Size;
|
||||
import android.view.Display;
|
||||
import android.view.DisplayInfo;
|
||||
import android.view.SurfaceHolder;
|
||||
|
||||
import com.android.systemui.glwallpaper.ImageWallpaperRenderer;
|
||||
import com.android.systemui.statusbar.phone.DozeParameters;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper
|
||||
public class ImageWallpaperTest extends SysuiTestCase {
|
||||
private static final int LOW_BMP_WIDTH = 128;
|
||||
private static final int LOW_BMP_HEIGHT = 128;
|
||||
private static final int INVALID_BMP_WIDTH = 1;
|
||||
private static final int INVALID_BMP_HEIGHT = 1;
|
||||
private static final int DISPLAY_WIDTH = 1920;
|
||||
private static final int DISPLAY_HEIGHT = 1080;
|
||||
|
||||
@Mock
|
||||
private SurfaceHolder mSurfaceHolder;
|
||||
@Mock
|
||||
private Context mMockContext;
|
||||
@Mock
|
||||
private Bitmap mWallpaperBitmap;
|
||||
@Mock
|
||||
private DozeParameters mDozeParam;
|
||||
|
||||
private CountDownLatch mEventCountdown;
|
||||
private CountDownLatch mAmbientEventCountdown;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mEventCountdown = new CountDownLatch(1);
|
||||
mAmbientEventCountdown = new CountDownLatch(2);
|
||||
|
||||
WallpaperManager wallpaperManager = mock(WallpaperManager.class);
|
||||
Resources resources = mock(Resources.class);
|
||||
|
||||
when(mMockContext.getSystemService(WallpaperManager.class)).thenReturn(wallpaperManager);
|
||||
when(mMockContext.getResources()).thenReturn(resources);
|
||||
when(resources.getConfiguration()).thenReturn(mock(Configuration.class));
|
||||
|
||||
DisplayInfo displayInfo = new DisplayInfo();
|
||||
displayInfo.logicalWidth = DISPLAY_WIDTH;
|
||||
displayInfo.logicalHeight = DISPLAY_HEIGHT;
|
||||
when(mMockContext.getDisplay()).thenReturn(
|
||||
new Display(mock(DisplayManagerGlobal.class), 0, displayInfo, (Resources) null));
|
||||
|
||||
when(wallpaperManager.getBitmap(false)).thenReturn(mWallpaperBitmap);
|
||||
when(mWallpaperBitmap.getColorSpace()).thenReturn(ColorSpace.get(ColorSpace.Named.SRGB));
|
||||
when(mWallpaperBitmap.getConfig()).thenReturn(Bitmap.Config.ARGB_8888);
|
||||
when(mDozeParam.getDisplayNeedsBlanking()).thenReturn(false);
|
||||
}
|
||||
|
||||
private ImageWallpaper createImageWallpaper() {
|
||||
return new ImageWallpaper(mDozeParam) {
|
||||
@Override
|
||||
public Engine onCreateEngine() {
|
||||
return new GLEngine(mMockContext, mDozeParam) {
|
||||
@Override
|
||||
public Context getDisplayContext() {
|
||||
return mMockContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SurfaceHolder getSurfaceHolder() {
|
||||
return mSurfaceHolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFixedSizeAllowed(boolean allowed) {
|
||||
super.setFixedSizeAllowed(allowed);
|
||||
assertWithMessage("mFixedSizeAllowed should be true").that(
|
||||
allowed).isTrue();
|
||||
mEventCountdown.countDown();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private ImageWallpaperRenderer createImageWallpaperRenderer(ImageWallpaper.GLEngine engine) {
|
||||
return new ImageWallpaperRenderer(mMockContext, engine) {
|
||||
@Override
|
||||
public void startProcessingImage() {
|
||||
loadBitmap();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeliversAmbientModeChanged() {
|
||||
//TODO: We need add tests for GLEngine.
|
||||
public void testBitmapWallpaper_normal() {
|
||||
// Will use a image wallpaper with dimensions DISPLAY_WIDTH x DISPLAY_WIDTH.
|
||||
// Then we expect the surface size will be also DISPLAY_WIDTH x DISPLAY_WIDTH.
|
||||
// Finally, we assert the transition will not be stopped.
|
||||
verifySurfaceSizeAndAssertTransition(DISPLAY_WIDTH /* bmpWidth */,
|
||||
DISPLAY_WIDTH /* bmpHeight */,
|
||||
DISPLAY_WIDTH /* surfaceWidth */,
|
||||
DISPLAY_WIDTH /* surfaceHeight */,
|
||||
false /* assertion */);
|
||||
}
|
||||
|
||||
// TODO: Add more test cases for GLEngine, tracing in b/124838911.
|
||||
@Test
|
||||
public void testBitmapWallpaper_low_resolution() {
|
||||
// Will use a image wallpaper with dimensions BMP_WIDTH x BMP_HEIGHT.
|
||||
// Then we expect the surface size will be also BMP_WIDTH x BMP_HEIGHT.
|
||||
// Finally, we assert the transition will be stopped.
|
||||
verifySurfaceSizeAndAssertTransition(LOW_BMP_WIDTH /* bmpWidth */,
|
||||
LOW_BMP_HEIGHT /* bmpHeight */,
|
||||
LOW_BMP_WIDTH /* surfaceWidth */,
|
||||
LOW_BMP_HEIGHT /* surfaceHeight */,
|
||||
true /* assertion */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBitmapWallpaper_too_small() {
|
||||
// Will use a image wallpaper with dimensions INVALID_BMP_WIDTH x INVALID_BMP_HEIGHT.
|
||||
// Then we expect the surface size will be also MIN_SURFACE_WIDTH x MIN_SURFACE_HEIGHT.
|
||||
// Finally, we assert the transition will be stopped.
|
||||
verifySurfaceSizeAndAssertTransition(INVALID_BMP_WIDTH /* bmpWidth */,
|
||||
INVALID_BMP_HEIGHT /* bmpHeight */,
|
||||
ImageWallpaper.GLEngine.MIN_SURFACE_WIDTH /* surfaceWidth */,
|
||||
ImageWallpaper.GLEngine.MIN_SURFACE_HEIGHT /* surfaceHeight */,
|
||||
true /* assertion */);
|
||||
}
|
||||
|
||||
private void verifySurfaceSizeAndAssertTransition(int bmpWidth, int bmpHeight,
|
||||
int surfaceWidth, int surfaceHeight, boolean assertion) {
|
||||
ImageWallpaper.GLEngine wallpaperEngine =
|
||||
(ImageWallpaper.GLEngine) createImageWallpaper().onCreateEngine();
|
||||
|
||||
ImageWallpaper.GLEngine engineSpy = spy(wallpaperEngine);
|
||||
when(engineSpy.mIsHighEndGfx).thenReturn(true);
|
||||
|
||||
when(mWallpaperBitmap.getWidth()).thenReturn(bmpWidth);
|
||||
when(mWallpaperBitmap.getHeight()).thenReturn(bmpHeight);
|
||||
|
||||
ImageWallpaperRenderer renderer = createImageWallpaperRenderer(engineSpy);
|
||||
doReturn(renderer).when(engineSpy).getRendererInstance();
|
||||
engineSpy.onCreate(engineSpy.getSurfaceHolder());
|
||||
|
||||
verify(mSurfaceHolder, times(1)).setFixedSize(surfaceWidth, surfaceHeight);
|
||||
assertWithMessage("setFixedSizeAllowed should have been called.").that(
|
||||
mEventCountdown.getCount()).isEqualTo(0);
|
||||
|
||||
Size frameSize = renderer.reportSurfaceSize();
|
||||
Rect frame = new Rect(0, 0, frameSize.getWidth(), frameSize.getHeight());
|
||||
when(mSurfaceHolder.getSurfaceFrame()).thenReturn(frame);
|
||||
|
||||
assertThat(engineSpy.checkIfShouldStopTransition()).isEqualTo(assertion);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user