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:
Ahan Wu
2020-01-27 20:39:57 +08:00
parent 7adf2907af
commit 4e4044060e
3 changed files with 216 additions and 31 deletions

View File

@@ -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");

View File

@@ -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) {

View File

@@ -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);
}
}