Merge "Fix ImageWallpaper memory regression"

This commit is contained in:
TreeHugger Robot
2020-02-12 03:28:47 +00:00
committed by Android (Google) Code Review
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);
}
}