Merge "Remove usage of GlEngine" into tm-qpr-dev am: 5538ee1c74

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20578407

Change-Id: I21aea86d44b63c3e53bd6dba528d7551cd34566e
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Aurélien Pomini
2022-12-13 11:29:27 +00:00
committed by Automerger Merge Worker
15 changed files with 61 additions and 1780 deletions

View File

@@ -23,11 +23,6 @@
xmlns:tools="http://schemas.android.com/tools"
coreApp="true">
<!-- Using OpenGL ES 2.0 -->
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- Used to read wallpaper -->

View File

@@ -1,11 +0,0 @@
precision mediump float;
// The actual wallpaper texture.
uniform sampler2D uTexture;
varying vec2 vTextureCoordinates;
void main() {
// gets the pixel value of the wallpaper for this uv coordinates on screen.
gl_FragColor = texture2D(uTexture, vTextureCoordinates);
}

View File

@@ -1,8 +0,0 @@
attribute vec4 aPosition;
attribute vec2 aTextureCoordinates;
varying vec2 vTextureCoordinates;
void main() {
vTextureCoordinates = aTextureCoordinates;
gl_Position = aPosition;
}

View File

@@ -254,10 +254,6 @@ object Flags {
// TODO(b/254512848): Tracking Bug
val REGION_SAMPLING = unreleasedFlag(801, "region_sampling", teamfood = true)
// 802 - wallpaper rendering
// TODO(b/254512923): Tracking Bug
@JvmField val USE_CANVAS_RENDERER = unreleasedFlag(802, "use_canvas_renderer")
// 803 - screen contents translation
// TODO(b/254513187): Tracking Bug
val SCREEN_CONTENTS_TRANSLATION = unreleasedFlag(803, "screen_contents_translation")

View File

@@ -16,8 +16,6 @@
package com.android.systemui.wallpapers;
import static com.android.systemui.flags.Flags.USE_CANVAS_RENDERER;
import android.app.WallpaperColors;
import android.app.WallpaperManager;
import android.graphics.Bitmap;
@@ -27,16 +25,10 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
import android.service.wallpaper.WallpaperService;
import android.util.ArraySet;
import android.util.Log;
import android.util.MathUtils;
import android.util.Size;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.WindowManager;
@@ -45,16 +37,10 @@ import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.wallpapers.canvas.WallpaperLocalColorExtractor;
import com.android.systemui.wallpapers.gl.EglHelper;
import com.android.systemui.wallpapers.gl.ImageWallpaperRenderer;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
@@ -64,455 +50,32 @@ import javax.inject.Inject;
*/
@SuppressWarnings({"UnusedDeclaration"})
public class ImageWallpaper extends WallpaperService {
private static final String TAG = ImageWallpaper.class.getSimpleName();
// We delayed destroy render context that subsequent render requests have chance to cancel it.
// This is to avoid destroying then recreating render context in a very short time.
private static final int DELAY_FINISH_RENDERING = 1000;
private static final @android.annotation.NonNull RectF LOCAL_COLOR_BOUNDS =
new RectF(0, 0, 1, 1);
private static final boolean DEBUG = false;
private final ArrayList<RectF> mLocalColorsToAdd = new ArrayList<>();
private final ArraySet<RectF> mColorAreas = new ArraySet<>();
// keep track of the number of pages of the launcher for local color extraction purposes
private volatile int mPages = 1;
private boolean mPagesComputed = false;
private HandlerThread mWorker;
// scaled down version
private Bitmap mMiniBitmap;
private final FeatureFlags mFeatureFlags;
// used in canvasEngine to load/unload the bitmap and extract the colors
// used for most tasks (call canvas.drawBitmap, load/unload the bitmap)
@Background
private final DelayableExecutor mBackgroundExecutor;
// wait at least this duration before unloading the bitmap
private static final int DELAY_UNLOAD_BITMAP = 2000;
@Inject
public ImageWallpaper(FeatureFlags featureFlags,
@Background DelayableExecutor backgroundExecutor) {
public ImageWallpaper(@Background DelayableExecutor backgroundExecutor) {
super();
mFeatureFlags = featureFlags;
mBackgroundExecutor = backgroundExecutor;
}
@Override
public void onCreate() {
super.onCreate();
mWorker = new HandlerThread(TAG);
mWorker.start();
}
@Override
public Engine onCreateEngine() {
return mFeatureFlags.isEnabled(USE_CANVAS_RENDERER) ? new CanvasEngine() : new GLEngine();
return new CanvasEngine();
}
@Override
public void onDestroy() {
super.onDestroy();
mWorker.quitSafely();
mWorker = null;
mMiniBitmap = null;
}
class GLEngine extends Engine implements DisplayListener {
// Surface is rejected if size below a threshold on some devices (ie. 8px on elfin)
// set min to 64 px (CTS covers this), please refer to ag/4867989 for detail.
@VisibleForTesting
static final int MIN_SURFACE_WIDTH = 128;
@VisibleForTesting
static final int MIN_SURFACE_HEIGHT = 128;
private ImageWallpaperRenderer mRenderer;
private EglHelper mEglHelper;
private final Runnable mFinishRenderingTask = this::finishRendering;
private boolean mNeedRedraw;
private boolean mDisplaySizeValid = false;
private int mDisplayWidth = 1;
private int mDisplayHeight = 1;
private int mImgWidth = 1;
private int mImgHeight = 1;
GLEngine() { }
@VisibleForTesting
GLEngine(Handler handler) {
super(SystemClock::elapsedRealtime, handler);
}
@Override
public void onCreate(SurfaceHolder surfaceHolder) {
Trace.beginSection("ImageWallpaper.Engine#onCreate");
mEglHelper = getEglHelperInstance();
// Deferred init renderer because we need to get wallpaper by display context.
mRenderer = getRendererInstance();
setFixedSizeAllowed(true);
updateSurfaceSize();
setShowForAllUsers(true);
mRenderer.setOnBitmapChanged(b -> {
mLocalColorsToAdd.addAll(mColorAreas);
if (mLocalColorsToAdd.size() > 0) {
updateMiniBitmapAndNotify(b);
}
});
getDisplayContext().getSystemService(DisplayManager.class)
.registerDisplayListener(this, mWorker.getThreadHandler());
Trace.endSection();
}
@Override
public void onDisplayAdded(int displayId) { }
@Override
public void onDisplayRemoved(int displayId) { }
@Override
public void onDisplayChanged(int displayId) {
if (displayId == getDisplayContext().getDisplayId()) {
mDisplaySizeValid = false;
}
}
EglHelper getEglHelperInstance() {
return new EglHelper();
}
ImageWallpaperRenderer getRendererInstance() {
return new ImageWallpaperRenderer(getDisplayContext());
}
@Override
public void onOffsetsChanged(float xOffset, float yOffset,
float xOffsetStep, float yOffsetStep,
int xPixelOffset, int yPixelOffset) {
final int pages;
if (xOffsetStep > 0 && xOffsetStep <= 1) {
pages = (int) Math.round(1 / xOffsetStep) + 1;
} else {
pages = 1;
}
if (pages == mPages) return;
mPages = pages;
if (mMiniBitmap == null || mMiniBitmap.isRecycled()) return;
mWorker.getThreadHandler().post(() ->
computeAndNotifyLocalColors(new ArrayList<>(mColorAreas), mMiniBitmap));
}
private void updateMiniBitmapAndNotify(Bitmap b) {
if (b == null) return;
int size = Math.min(b.getWidth(), b.getHeight());
float scale = 1.0f;
if (size > MIN_SURFACE_WIDTH) {
scale = (float) MIN_SURFACE_WIDTH / (float) size;
}
mImgHeight = b.getHeight();
mImgWidth = b.getWidth();
mMiniBitmap = Bitmap.createScaledBitmap(b, (int) Math.max(scale * b.getWidth(), 1),
(int) Math.max(scale * b.getHeight(), 1), false);
computeAndNotifyLocalColors(mLocalColorsToAdd, mMiniBitmap);
mLocalColorsToAdd.clear();
}
private void updateSurfaceSize() {
Trace.beginSection("ImageWallpaper#updateSurfaceSize");
SurfaceHolder holder = getSurfaceHolder();
Size frameSize = mRenderer.reportSurfaceSize();
int width = Math.max(MIN_SURFACE_WIDTH, frameSize.getWidth());
int height = Math.max(MIN_SURFACE_HEIGHT, frameSize.getHeight());
holder.setFixedSize(width, height);
Trace.endSection();
}
@Override
public boolean shouldZoomOutWallpaper() {
return true;
}
@Override
public boolean shouldWaitForEngineShown() {
return true;
}
@Override
public void onDestroy() {
getDisplayContext().getSystemService(DisplayManager.class)
.unregisterDisplayListener(this);
mMiniBitmap = null;
mWorker.getThreadHandler().post(() -> {
mRenderer.finish();
mRenderer = null;
mEglHelper.finish();
mEglHelper = null;
});
}
@Override
public boolean supportsLocalColorExtraction() {
return true;
}
@Override
public void addLocalColorsAreas(@NonNull List<RectF> regions) {
mWorker.getThreadHandler().post(() -> {
if (mColorAreas.size() + mLocalColorsToAdd.size() == 0) {
setOffsetNotificationsEnabled(true);
}
Bitmap bitmap = mMiniBitmap;
if (bitmap == null) {
mLocalColorsToAdd.addAll(regions);
if (mRenderer != null) mRenderer.use(this::updateMiniBitmapAndNotify);
} else {
computeAndNotifyLocalColors(regions, bitmap);
}
});
}
private void computeAndNotifyLocalColors(@NonNull List<RectF> regions, Bitmap b) {
List<WallpaperColors> colors = getLocalWallpaperColors(regions, b);
mColorAreas.addAll(regions);
try {
notifyLocalColorsChanged(regions, colors);
} catch (RuntimeException e) {
Log.e(TAG, e.getMessage(), e);
}
}
@Override
public void removeLocalColorsAreas(@NonNull List<RectF> regions) {
mWorker.getThreadHandler().post(() -> {
mColorAreas.removeAll(regions);
mLocalColorsToAdd.removeAll(regions);
if (mColorAreas.size() + mLocalColorsToAdd.size() == 0) {
setOffsetNotificationsEnabled(false);
}
});
}
/**
* Transform the logical coordinates into wallpaper coordinates.
*
* Logical coordinates are organised such that the various pages are non-overlapping. So,
* if there are n pages, the first page will have its X coordinate on the range [0-1/n].
*
* The real pages are overlapping. If the Wallpaper are a width Ww and the screen a width
* Ws, the relative width of a page Wr is Ws/Ww. This does not change if the number of
* pages increase.
* If there are n pages, the page k starts at the offset k * (1 - Wr) / (n - 1), as the
* last page is at position (1-Wr) and the others are regularly spread on the range [0-
* (1-Wr)].
*/
private RectF pageToImgRect(RectF area) {
if (!mDisplaySizeValid) {
Rect window = getDisplayContext()
.getSystemService(WindowManager.class)
.getCurrentWindowMetrics()
.getBounds();
mDisplayWidth = window.width();
mDisplayHeight = window.height();
mDisplaySizeValid = true;
}
// Width of a page for the caller of this API.
float virtualPageWidth = 1f / (float) mPages;
float leftPosOnPage = (area.left % virtualPageWidth) / virtualPageWidth;
float rightPosOnPage = (area.right % virtualPageWidth) / virtualPageWidth;
int currentPage = (int) Math.floor(area.centerX() / virtualPageWidth);
RectF imgArea = new RectF();
if (mImgWidth == 0 || mImgHeight == 0 || mDisplayWidth <= 0 || mDisplayHeight <= 0) {
return imgArea;
}
imgArea.bottom = area.bottom;
imgArea.top = area.top;
float imageScale = Math.min(((float) mImgHeight) / mDisplayHeight, 1);
float mappedScreenWidth = mDisplayWidth * imageScale;
float pageWidth = Math.min(1.0f,
mImgWidth > 0 ? mappedScreenWidth / (float) mImgWidth : 1.f);
float pageOffset = (1 - pageWidth) / (float) (mPages - 1);
imgArea.left = MathUtils.constrain(
leftPosOnPage * pageWidth + currentPage * pageOffset, 0, 1);
imgArea.right = MathUtils.constrain(
rightPosOnPage * pageWidth + currentPage * pageOffset, 0, 1);
if (imgArea.left > imgArea.right) {
// take full page
imgArea.left = 0;
imgArea.right = 1;
}
return imgArea;
}
private List<WallpaperColors> getLocalWallpaperColors(@NonNull List<RectF> areas,
Bitmap b) {
List<WallpaperColors> colors = new ArrayList<>(areas.size());
for (int i = 0; i < areas.size(); i++) {
RectF area = pageToImgRect(areas.get(i));
if (area == null || !LOCAL_COLOR_BOUNDS.contains(area)) {
colors.add(null);
continue;
}
Rect subImage = new Rect(
(int) Math.floor(area.left * b.getWidth()),
(int) Math.floor(area.top * b.getHeight()),
(int) Math.ceil(area.right * b.getWidth()),
(int) Math.ceil(area.bottom * b.getHeight()));
if (subImage.isEmpty()) {
// Do not notify client. treat it as too small to sample
colors.add(null);
continue;
}
Bitmap colorImg = Bitmap.createBitmap(b,
subImage.left, subImage.top, subImage.width(), subImage.height());
WallpaperColors color = WallpaperColors.fromBitmap(colorImg);
colors.add(color);
}
return colors;
}
@Override
public void onSurfaceCreated(SurfaceHolder holder) {
if (mWorker == null) return;
mWorker.getThreadHandler().post(() -> {
Trace.beginSection("ImageWallpaper#onSurfaceCreated");
mEglHelper.init(holder, needSupportWideColorGamut());
mRenderer.onSurfaceCreated();
Trace.endSection();
});
}
@Override
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if (mWorker == null) return;
mWorker.getThreadHandler().post(() -> mRenderer.onSurfaceChanged(width, height));
}
@Override
public void onSurfaceRedrawNeeded(SurfaceHolder holder) {
if (mWorker == null) return;
mWorker.getThreadHandler().post(this::drawFrame);
}
private void drawFrame() {
Trace.beginSection("ImageWallpaper#drawFrame");
preRender();
requestRender();
postRender();
Trace.endSection();
}
public void preRender() {
// This method should only be invoked from worker thread.
Trace.beginSection("ImageWallpaper#preRender");
preRenderInternal();
Trace.endSection();
}
private void preRenderInternal() {
boolean contextRecreated = false;
Rect frame = getSurfaceHolder().getSurfaceFrame();
cancelFinishRenderingTask();
// Check if we need to recreate egl context.
if (!mEglHelper.hasEglContext()) {
mEglHelper.destroyEglSurface();
if (!mEglHelper.createEglContext()) {
Log.w(TAG, "recreate egl context failed!");
} else {
contextRecreated = true;
}
}
// Check if we need to recreate egl surface.
if (mEglHelper.hasEglContext() && !mEglHelper.hasEglSurface()) {
if (!mEglHelper.createEglSurface(getSurfaceHolder(), needSupportWideColorGamut())) {
Log.w(TAG, "recreate egl surface failed!");
}
}
// If we recreate egl context, notify renderer to setup again.
if (mEglHelper.hasEglContext() && mEglHelper.hasEglSurface() && contextRecreated) {
mRenderer.onSurfaceCreated();
mRenderer.onSurfaceChanged(frame.width(), frame.height());
}
}
public void requestRender() {
// This method should only be invoked from worker thread.
Trace.beginSection("ImageWallpaper#requestRender");
requestRenderInternal();
Trace.endSection();
}
private void requestRenderInternal() {
Rect frame = getSurfaceHolder().getSurfaceFrame();
boolean readyToRender = mEglHelper.hasEglContext() && mEglHelper.hasEglSurface()
&& frame.width() > 0 && frame.height() > 0;
if (readyToRender) {
mRenderer.onDrawFrame();
if (!mEglHelper.swapBuffer()) {
Log.e(TAG, "drawFrame failed!");
}
} else {
Log.e(TAG, "requestRender: not ready, has context=" + mEglHelper.hasEglContext()
+ ", has surface=" + mEglHelper.hasEglSurface()
+ ", frame=" + frame);
}
}
public void postRender() {
// This method should only be invoked from worker thread.
scheduleFinishRendering();
reportEngineShown(false /* waitForEngineShown */);
}
private void cancelFinishRenderingTask() {
if (mWorker == null) return;
mWorker.getThreadHandler().removeCallbacks(mFinishRenderingTask);
}
private void scheduleFinishRendering() {
if (mWorker == null) return;
cancelFinishRenderingTask();
mWorker.getThreadHandler().postDelayed(mFinishRenderingTask, DELAY_FINISH_RENDERING);
}
private void finishRendering() {
Trace.beginSection("ImageWallpaper#finishRendering");
if (mEglHelper != null) {
mEglHelper.destroyEglSurface();
mEglHelper.destroyEglContext();
}
Trace.endSection();
}
private boolean needSupportWideColorGamut() {
return mRenderer.isWcgContent();
}
@Override
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);
out.print(prefix); out.print("valid surface=");
out.println(getSurfaceHolder() != null && getSurfaceHolder().getSurface() != null
? getSurfaceHolder().getSurface().isValid()
: "null");
out.print(prefix); out.print("surface frame=");
out.println(getSurfaceHolder() != null ? getSurfaceHolder().getSurfaceFrame() : "null");
mEglHelper.dump(prefix, fd, out, args);
mRenderer.dump(prefix, fd, out, args);
}
}
class CanvasEngine extends WallpaperService.Engine implements DisplayListener {
private WallpaperManager mWallpaperManager;
private final WallpaperLocalColorExtractor mWallpaperLocalColorExtractor;
@@ -736,13 +299,8 @@ public class ImageWallpaper extends WallpaperService {
// be loaded, we will go into a cycle. Don't do a build where the
// default wallpaper can't be loaded.
Log.w(TAG, "Unable to load wallpaper!", exception);
try {
mWallpaperManager.clear(WallpaperManager.FLAG_SYSTEM);
} catch (IOException ex) {
// now we're really screwed.
Log.w(TAG, "Unable reset to default wallpaper!", ex);
}
mWallpaperManager.clearWallpaper(
WallpaperManager.FLAG_SYSTEM, UserHandle.USER_CURRENT);
try {
bitmap = mWallpaperManager.getBitmapAsUser(UserHandle.USER_CURRENT, false);
} catch (RuntimeException | OutOfMemoryError e) {
@@ -868,7 +426,6 @@ public class ImageWallpaper extends WallpaperService {
mWallpaperLocalColorExtractor.setDisplayDimensions(window.width(), window.height());
}
@Override
protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
super.dump(prefix, fd, out, args);

View File

@@ -15,7 +15,7 @@
*/
package com.android.systemui.wallpapers.canvas;
package com.android.systemui.wallpapers;
import android.app.WallpaperColors;
import android.graphics.Bitmap;
@@ -31,7 +31,6 @@ import androidx.annotation.VisibleForTesting;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.util.Assert;
import com.android.systemui.wallpapers.ImageWallpaper;
import java.io.FileDescriptor;
import java.io.PrintWriter;

View File

@@ -1,380 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.wallpapers.gl;
import static android.opengl.EGL14.EGL_ALPHA_SIZE;
import static android.opengl.EGL14.EGL_BLUE_SIZE;
import static android.opengl.EGL14.EGL_CONFIG_CAVEAT;
import static android.opengl.EGL14.EGL_CONTEXT_CLIENT_VERSION;
import static android.opengl.EGL14.EGL_DEFAULT_DISPLAY;
import static android.opengl.EGL14.EGL_DEPTH_SIZE;
import static android.opengl.EGL14.EGL_EXTENSIONS;
import static android.opengl.EGL14.EGL_GREEN_SIZE;
import static android.opengl.EGL14.EGL_NONE;
import static android.opengl.EGL14.EGL_NO_CONTEXT;
import static android.opengl.EGL14.EGL_NO_DISPLAY;
import static android.opengl.EGL14.EGL_NO_SURFACE;
import static android.opengl.EGL14.EGL_OPENGL_ES2_BIT;
import static android.opengl.EGL14.EGL_RED_SIZE;
import static android.opengl.EGL14.EGL_RENDERABLE_TYPE;
import static android.opengl.EGL14.EGL_STENCIL_SIZE;
import static android.opengl.EGL14.EGL_SUCCESS;
import static android.opengl.EGL14.eglChooseConfig;
import static android.opengl.EGL14.eglCreateContext;
import static android.opengl.EGL14.eglCreateWindowSurface;
import static android.opengl.EGL14.eglDestroyContext;
import static android.opengl.EGL14.eglDestroySurface;
import static android.opengl.EGL14.eglGetDisplay;
import static android.opengl.EGL14.eglGetError;
import static android.opengl.EGL14.eglInitialize;
import static android.opengl.EGL14.eglMakeCurrent;
import static android.opengl.EGL14.eglQueryString;
import static android.opengl.EGL14.eglSwapBuffers;
import static android.opengl.EGL14.eglTerminate;
import android.opengl.EGLConfig;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLSurface;
import android.opengl.GLUtils;
import android.text.TextUtils;
import android.util.Log;
import android.view.SurfaceHolder;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* A helper class to handle EGL management.
*/
public class EglHelper {
private static final String TAG = EglHelper.class.getSimpleName();
private static final int OPENGLES_VERSION = 2;
// Below two constants make drawing at low priority, so other things can preempt our drawing.
private static final int EGL_CONTEXT_PRIORITY_LEVEL_IMG = 0x3100;
private static final int EGL_CONTEXT_PRIORITY_LOW_IMG = 0x3103;
private static final boolean DEBUG = true;
private static final int EGL_GL_COLORSPACE_KHR = 0x309D;
private static final int EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT = 0x3490;
private static final String EGL_IMG_CONTEXT_PRIORITY = "EGL_IMG_context_priority";
/**
* https://www.khronos.org/registry/EGL/extensions/KHR/EGL_KHR_gl_colorspace.txt
*/
private static final String KHR_GL_COLOR_SPACE = "EGL_KHR_gl_colorspace";
/**
* https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_gl_colorspace_display_p3_passthrough.txt
*/
private static final String EXT_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH =
"EGL_EXT_gl_colorspace_display_p3_passthrough";
private EGLDisplay mEglDisplay;
private EGLConfig mEglConfig;
private EGLContext mEglContext;
private EGLSurface mEglSurface;
private final int[] mEglVersion = new int[2];
private boolean mEglReady;
private final Set<String> mExts;
public EglHelper() {
mExts = new HashSet<>();
connectDisplay();
}
/**
* Initialize render context.
* @param surfaceHolder surface holder.
* @param wideColorGamut claim if a wcg surface is necessary.
* @return true if the render context is ready.
*/
public boolean init(SurfaceHolder surfaceHolder, boolean wideColorGamut) {
if (!hasEglDisplay() && !connectDisplay()) {
Log.w(TAG, "Can not connect display, abort!");
return false;
}
if (!eglInitialize(mEglDisplay, mEglVersion, 0 /* majorOffset */,
mEglVersion, 1 /* minorOffset */)) {
Log.w(TAG, "eglInitialize failed: " + GLUtils.getEGLErrorString(eglGetError()));
return false;
}
mEglConfig = chooseEglConfig();
if (mEglConfig == null) {
Log.w(TAG, "eglConfig not initialized!");
return false;
}
if (!createEglContext()) {
Log.w(TAG, "Can't create EGLContext!");
return false;
}
if (!createEglSurface(surfaceHolder, wideColorGamut)) {
Log.w(TAG, "Can't create EGLSurface!");
return false;
}
mEglReady = true;
return true;
}
private boolean connectDisplay() {
mExts.clear();
mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (!hasEglDisplay()) {
Log.w(TAG, "eglGetDisplay failed: " + GLUtils.getEGLErrorString(eglGetError()));
return false;
}
String queryString = eglQueryString(mEglDisplay, EGL_EXTENSIONS);
if (!TextUtils.isEmpty(queryString)) {
Collections.addAll(mExts, queryString.split(" "));
}
return true;
}
boolean checkExtensionCapability(String extName) {
return mExts.contains(extName);
}
int getWcgCapability() {
if (checkExtensionCapability(EXT_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH)) {
return EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT;
}
return 0;
}
private EGLConfig chooseEglConfig() {
int[] configsCount = new int[1];
EGLConfig[] configs = new EGLConfig[1];
int[] configSpec = getConfig();
if (!eglChooseConfig(mEglDisplay, configSpec, 0, configs, 0, 1, configsCount, 0)) {
Log.w(TAG, "eglChooseConfig failed: " + GLUtils.getEGLErrorString(eglGetError()));
return null;
} else {
if (configsCount[0] <= 0) {
Log.w(TAG, "eglChooseConfig failed, invalid configs count: " + configsCount[0]);
return null;
} else {
return configs[0];
}
}
}
private int[] getConfig() {
return new int[] {
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 0,
EGL_DEPTH_SIZE, 0,
EGL_STENCIL_SIZE, 0,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_CONFIG_CAVEAT, EGL_NONE,
EGL_NONE
};
}
/**
* Prepare an EglSurface.
* @param surfaceHolder surface holder.
* @param wcg if need to support wcg.
* @return true if EglSurface is ready.
*/
public boolean createEglSurface(SurfaceHolder surfaceHolder, boolean wcg) {
if (DEBUG) {
Log.d(TAG, "createEglSurface start");
}
if (hasEglDisplay() && surfaceHolder.getSurface().isValid()) {
int[] attrs = null;
int wcgCapability = getWcgCapability();
if (wcg && checkExtensionCapability(KHR_GL_COLOR_SPACE) && wcgCapability > 0) {
attrs = new int[] {EGL_GL_COLORSPACE_KHR, wcgCapability, EGL_NONE};
}
mEglSurface = askCreatingEglWindowSurface(surfaceHolder, attrs, 0 /* offset */);
} else {
Log.w(TAG, "Create EglSurface failed: hasEglDisplay=" + hasEglDisplay()
+ ", has valid surface=" + surfaceHolder.getSurface().isValid());
return false;
}
if (!hasEglSurface()) {
Log.w(TAG, "createWindowSurface failed: " + GLUtils.getEGLErrorString(eglGetError()));
return false;
}
if (!eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
Log.w(TAG, "eglMakeCurrent failed: " + GLUtils.getEGLErrorString(eglGetError()));
return false;
}
if (DEBUG) {
Log.d(TAG, "createEglSurface done");
}
return true;
}
EGLSurface askCreatingEglWindowSurface(SurfaceHolder holder, int[] attrs, int offset) {
return eglCreateWindowSurface(mEglDisplay, mEglConfig, holder, attrs, offset);
}
/**
* Destroy EglSurface.
*/
public void destroyEglSurface() {
if (hasEglSurface()) {
eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroySurface(mEglDisplay, mEglSurface);
mEglSurface = EGL_NO_SURFACE;
}
}
/**
* Check if we have a valid EglSurface.
* @return true if EglSurface is ready.
*/
public boolean hasEglSurface() {
return mEglSurface != null && mEglSurface != EGL_NO_SURFACE;
}
/**
* Prepare EglContext.
* @return true if EglContext is ready.
*/
public boolean createEglContext() {
if (DEBUG) {
Log.d(TAG, "createEglContext start");
}
int[] attrib_list = new int[5];
int idx = 0;
attrib_list[idx++] = EGL_CONTEXT_CLIENT_VERSION;
attrib_list[idx++] = OPENGLES_VERSION;
if (checkExtensionCapability(EGL_IMG_CONTEXT_PRIORITY)) {
attrib_list[idx++] = EGL_CONTEXT_PRIORITY_LEVEL_IMG;
attrib_list[idx++] = EGL_CONTEXT_PRIORITY_LOW_IMG;
}
attrib_list[idx] = EGL_NONE;
if (hasEglDisplay()) {
mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, attrib_list, 0);
} else {
Log.w(TAG, "mEglDisplay is null");
return false;
}
if (!hasEglContext()) {
Log.w(TAG, "eglCreateContext failed: " + GLUtils.getEGLErrorString(eglGetError()));
return false;
}
if (DEBUG) {
Log.d(TAG, "createEglContext done");
}
return true;
}
/**
* Destroy EglContext.
*/
public void destroyEglContext() {
if (hasEglContext()) {
eglDestroyContext(mEglDisplay, mEglContext);
mEglContext = EGL_NO_CONTEXT;
}
}
/**
* Check if we have EglContext.
* @return true if EglContext is ready.
*/
public boolean hasEglContext() {
return mEglContext != null && mEglContext != EGL_NO_CONTEXT;
}
/**
* Check if we have EglDisplay.
* @return true if EglDisplay is ready.
*/
public boolean hasEglDisplay() {
return mEglDisplay != null && mEglDisplay != EGL_NO_DISPLAY;
}
/**
* Swap buffer to display.
* @return true if swap successfully.
*/
public boolean swapBuffer() {
boolean status = eglSwapBuffers(mEglDisplay, mEglSurface);
int error = eglGetError();
if (error != EGL_SUCCESS) {
Log.w(TAG, "eglSwapBuffers failed: " + GLUtils.getEGLErrorString(error));
}
return status;
}
/**
* Destroy EglSurface and EglContext, then terminate EGL.
*/
public void finish() {
if (hasEglSurface()) {
destroyEglSurface();
}
if (hasEglContext()) {
destroyEglContext();
}
if (hasEglDisplay()) {
terminateEglDisplay();
}
mEglReady = false;
}
void terminateEglDisplay() {
eglTerminate(mEglDisplay);
mEglDisplay = EGL_NO_DISPLAY;
}
/**
* Called to dump current state.
* @param prefix prefix.
* @param fd fd.
* @param out out.
* @param args args.
*/
public void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
String eglVersion = mEglVersion[0] + "." + mEglVersion[1];
out.print(prefix); out.print("EGL version="); out.print(eglVersion);
out.print(", "); out.print("EGL ready="); out.print(mEglReady);
out.print(", "); out.print("has EglContext="); out.print(hasEglContext());
out.print(", "); out.print("has EglSurface="); out.println(hasEglSurface());
int[] configs = getConfig();
StringBuilder sb = new StringBuilder();
sb.append('{');
for (int egl : configs) {
sb.append("0x").append(Integer.toHexString(egl)).append(",");
}
sb.setCharAt(sb.length() - 1, '}');
out.print(prefix); out.print("EglConfig="); out.println(sb.toString());
}
}

View File

@@ -1,70 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.wallpapers.gl;
import android.util.Size;
import java.io.FileDescriptor;
import java.io.PrintWriter;
/**
* A renderer which is responsible for making OpenGL calls to render a frame.
*/
public interface GLWallpaperRenderer {
/**
* Check if the content to render is a WCG content.
*/
boolean isWcgContent();
/**
* Called when the surface is created or recreated.
*/
void onSurfaceCreated();
/**
* Called when the surface changed size.
* @param width surface width.
* @param height surface height.
*/
void onSurfaceChanged(int width, int height);
/**
* Called to draw the current frame.
*/
void onDrawFrame();
/**
* Ask renderer to report the surface size it needs.
*/
Size reportSurfaceSize();
/**
* Called when no need to render any more.
*/
void finish();
/**
* Called to dump current state.
* @param prefix prefix.
* @param fd fd.
* @param out out.
* @param args args.
*/
void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args);
}

View File

@@ -1,115 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.wallpapers.gl;
import static android.opengl.GLES20.GL_FRAGMENT_SHADER;
import static android.opengl.GLES20.GL_VERTEX_SHADER;
import static android.opengl.GLES20.glAttachShader;
import static android.opengl.GLES20.glCompileShader;
import static android.opengl.GLES20.glCreateProgram;
import static android.opengl.GLES20.glCreateShader;
import static android.opengl.GLES20.glGetAttribLocation;
import static android.opengl.GLES20.glGetUniformLocation;
import static android.opengl.GLES20.glLinkProgram;
import static android.opengl.GLES20.glShaderSource;
import static android.opengl.GLES20.glUseProgram;
import android.content.Context;
import android.content.res.Resources;
import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* This class takes charge of linking shader codes and then return a handle for OpenGL ES program.
*/
class ImageGLProgram {
private static final String TAG = ImageGLProgram.class.getSimpleName();
private Context mContext;
private int mProgramHandle;
ImageGLProgram(Context context) {
mContext = context.getApplicationContext();
}
private int loadShaderProgram(int vertexId, int fragmentId) {
final String vertexSrc = getShaderResource(vertexId);
final String fragmentSrc = getShaderResource(fragmentId);
final int vertexHandle = getShaderHandle(GL_VERTEX_SHADER, vertexSrc);
final int fragmentHandle = getShaderHandle(GL_FRAGMENT_SHADER, fragmentSrc);
return getProgramHandle(vertexHandle, fragmentHandle);
}
private String getShaderResource(int shaderId) {
Resources res = mContext.getResources();
StringBuilder code = new StringBuilder();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(res.openRawResource(shaderId)))) {
String nextLine;
while ((nextLine = reader.readLine()) != null) {
code.append(nextLine).append("\n");
}
} catch (IOException | Resources.NotFoundException ex) {
Log.d(TAG, "Can not read the shader source", ex);
code = null;
}
return code == null ? "" : code.toString();
}
private int getShaderHandle(int type, String src) {
final int shader = glCreateShader(type);
if (shader == 0) {
Log.d(TAG, "Create shader failed, type=" + type);
return 0;
}
glShaderSource(shader, src);
glCompileShader(shader);
return shader;
}
private int getProgramHandle(int vertexHandle, int fragmentHandle) {
final int program = glCreateProgram();
if (program == 0) {
Log.d(TAG, "Can not create OpenGL ES program");
return 0;
}
glAttachShader(program, vertexHandle);
glAttachShader(program, fragmentHandle);
glLinkProgram(program);
return program;
}
boolean useGLProgram(int vertexResId, int fragmentResId) {
mProgramHandle = loadShaderProgram(vertexResId, fragmentResId);
glUseProgram(mProgramHandle);
return true;
}
int getAttributeHandle(String name) {
return glGetAttribLocation(mProgramHandle, name);
}
int getUniformHandle(String name) {
return glGetUniformLocation(mProgramHandle, name);
}
}

View File

@@ -1,183 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.wallpapers.gl;
import static android.opengl.GLES20.GL_FLOAT;
import static android.opengl.GLES20.GL_LINEAR;
import static android.opengl.GLES20.GL_TEXTURE0;
import static android.opengl.GLES20.GL_TEXTURE_2D;
import static android.opengl.GLES20.GL_TEXTURE_MAG_FILTER;
import static android.opengl.GLES20.GL_TEXTURE_MIN_FILTER;
import static android.opengl.GLES20.GL_TRIANGLES;
import static android.opengl.GLES20.glActiveTexture;
import static android.opengl.GLES20.glBindTexture;
import static android.opengl.GLES20.glDrawArrays;
import static android.opengl.GLES20.glEnableVertexAttribArray;
import static android.opengl.GLES20.glGenTextures;
import static android.opengl.GLES20.glTexParameteri;
import static android.opengl.GLES20.glUniform1i;
import static android.opengl.GLES20.glVertexAttribPointer;
import android.graphics.Bitmap;
import android.opengl.GLUtils;
import android.util.Log;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
/**
* This class takes charge of the geometry data like vertices and texture coordinates.
* It delivers these data to opengl runtime and triggers draw calls if necessary.
*/
class ImageGLWallpaper {
private static final String TAG = ImageGLWallpaper.class.getSimpleName();
private static final String A_POSITION = "aPosition";
private static final String A_TEXTURE_COORDINATES = "aTextureCoordinates";
private static final String U_TEXTURE = "uTexture";
private static final int POSITION_COMPONENT_COUNT = 2;
private static final int TEXTURE_COMPONENT_COUNT = 2;
private static final int BYTES_PER_FLOAT = 4;
// Vertices to define the square with 2 triangles.
private static final float[] VERTICES = {
-1.0f, -1.0f,
+1.0f, -1.0f,
+1.0f, +1.0f,
+1.0f, +1.0f,
-1.0f, +1.0f,
-1.0f, -1.0f
};
// Texture coordinates that maps to vertices.
private static final float[] TEXTURES = {
0f, 1f,
1f, 1f,
1f, 0f,
1f, 0f,
0f, 0f,
0f, 1f
};
private final FloatBuffer mVertexBuffer;
private final FloatBuffer mTextureBuffer;
private final ImageGLProgram mProgram;
private int mAttrPosition;
private int mAttrTextureCoordinates;
private int mUniTexture;
private int mTextureId;
ImageGLWallpaper(ImageGLProgram program) {
mProgram = program;
// Create an float array in opengles runtime (native) and put vertex data.
mVertexBuffer = ByteBuffer.allocateDirect(VERTICES.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
mVertexBuffer.put(VERTICES);
mVertexBuffer.position(0);
// Create an float array in opengles runtime (native) and put texture data.
mTextureBuffer = ByteBuffer.allocateDirect(TEXTURES.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
mTextureBuffer.put(TEXTURES);
mTextureBuffer.position(0);
}
void setup(Bitmap bitmap) {
setupAttributes();
setupUniforms();
setupTexture(bitmap);
}
private void setupAttributes() {
mAttrPosition = mProgram.getAttributeHandle(A_POSITION);
mVertexBuffer.position(0);
glVertexAttribPointer(mAttrPosition, POSITION_COMPONENT_COUNT, GL_FLOAT,
false, 0, mVertexBuffer);
glEnableVertexAttribArray(mAttrPosition);
mAttrTextureCoordinates = mProgram.getAttributeHandle(A_TEXTURE_COORDINATES);
mTextureBuffer.position(0);
glVertexAttribPointer(mAttrTextureCoordinates, TEXTURE_COMPONENT_COUNT, GL_FLOAT,
false, 0, mTextureBuffer);
glEnableVertexAttribArray(mAttrTextureCoordinates);
}
private void setupUniforms() {
mUniTexture = mProgram.getUniformHandle(U_TEXTURE);
}
void draw() {
glDrawArrays(GL_TRIANGLES, 0, VERTICES.length / 2);
}
private void setupTexture(Bitmap bitmap) {
final int[] tids = new int[1];
if (bitmap == null || bitmap.isRecycled()) {
Log.w(TAG, "setupTexture: invalid bitmap");
return;
}
// Generate one texture object and store the id in tids[0].
glGenTextures(1, tids, 0);
if (tids[0] == 0) {
Log.w(TAG, "setupTexture: glGenTextures() failed");
return;
}
try {
// Bind a named texture to a target.
glBindTexture(GL_TEXTURE_2D, tids[0]);
// Load the bitmap data and copy it over into the texture object
// that is currently bound.
GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
// Use bilinear texture filtering when minification.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// Use bilinear texture filtering when magnification.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
mTextureId = tids[0];
} catch (IllegalArgumentException e) {
Log.w(TAG, "Failed uploading texture: " + e.getLocalizedMessage());
}
}
void useTexture() {
// Set the active texture unit to texture unit 0.
glActiveTexture(GL_TEXTURE0);
// Bind the texture to this unit.
glBindTexture(GL_TEXTURE_2D, mTextureId);
// Let the texture sampler in fragment shader to read form this texture unit.
glUniform1i(mUniTexture, 0);
}
/**
* Called to dump current state.
* @param prefix prefix.
* @param fd fd.
* @param out out.
* @param args args.
*/
public void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
}
}

View File

@@ -1,195 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.wallpapers.gl;
import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;
import static android.opengl.GLES20.glClear;
import static android.opengl.GLES20.glClearColor;
import static android.opengl.GLES20.glViewport;
import android.app.WallpaperManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.os.UserHandle;
import android.util.Log;
import android.util.Size;
import com.android.systemui.R;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
/**
* A GL renderer for image wallpaper.
*/
public class ImageWallpaperRenderer implements GLWallpaperRenderer {
private static final String TAG = ImageWallpaperRenderer.class.getSimpleName();
private static final boolean DEBUG = false;
private final ImageGLProgram mProgram;
private final ImageGLWallpaper mWallpaper;
private final Rect mSurfaceSize = new Rect();
private final WallpaperTexture mTexture;
private Consumer<Bitmap> mOnBitmapUpdated;
public ImageWallpaperRenderer(Context context) {
final WallpaperManager wpm = context.getSystemService(WallpaperManager.class);
if (wpm == null) {
Log.w(TAG, "WallpaperManager not available");
}
mTexture = new WallpaperTexture(wpm);
mProgram = new ImageGLProgram(context);
mWallpaper = new ImageGLWallpaper(mProgram);
}
/**
* @hide
*/
public void setOnBitmapChanged(Consumer<Bitmap> c) {
mOnBitmapUpdated = c;
}
/**
* @hide
*/
public void use(Consumer<Bitmap> c) {
mTexture.use(c);
}
@Override
public boolean isWcgContent() {
return mTexture.isWcgContent();
}
@Override
public void onSurfaceCreated() {
glClearColor(0f, 0f, 0f, 1.0f);
mProgram.useGLProgram(
R.raw.image_wallpaper_vertex_shader, R.raw.image_wallpaper_fragment_shader);
mTexture.use(bitmap -> {
if (bitmap == null) {
Log.w(TAG, "reload texture failed!");
} else if (mOnBitmapUpdated != null) {
mOnBitmapUpdated.accept(bitmap);
}
mWallpaper.setup(bitmap);
});
}
@Override
public void onSurfaceChanged(int width, int height) {
glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame() {
glClear(GL_COLOR_BUFFER_BIT);
glViewport(0, 0, mSurfaceSize.width(), mSurfaceSize.height());
mWallpaper.useTexture();
mWallpaper.draw();
}
@Override
public Size reportSurfaceSize() {
mSurfaceSize.set(mTexture.getTextureDimensions());
return new Size(mSurfaceSize.width(), mSurfaceSize.height());
}
@Override
public void finish() {
}
@Override
public void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
out.print(prefix); out.print("mSurfaceSize="); out.print(mSurfaceSize);
out.print(prefix); out.print("mWcgContent="); out.print(isWcgContent());
mWallpaper.dump(prefix, fd, out, args);
}
static class WallpaperTexture {
private final AtomicInteger mRefCount;
private final Rect mDimensions;
private final WallpaperManager mWallpaperManager;
private Bitmap mBitmap;
private boolean mWcgContent;
private boolean mTextureUsed;
private WallpaperTexture(WallpaperManager wallpaperManager) {
mWallpaperManager = wallpaperManager;
mRefCount = new AtomicInteger();
mDimensions = new Rect();
}
public void use(Consumer<Bitmap> consumer) {
mRefCount.incrementAndGet();
synchronized (mRefCount) {
if (mBitmap == null) {
mBitmap = mWallpaperManager.getBitmapAsUser(UserHandle.USER_CURRENT,
false /* hardware */);
mWcgContent = mWallpaperManager.wallpaperSupportsWcg(
WallpaperManager.FLAG_SYSTEM);
mWallpaperManager.forgetLoadedWallpaper();
if (mBitmap != null) {
mDimensions.set(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
mTextureUsed = true;
} else {
Log.w(TAG, "Can't get bitmap");
}
}
}
if (consumer != null) {
consumer.accept(mBitmap);
}
synchronized (mRefCount) {
final int count = mRefCount.decrementAndGet();
if (count == 0 && mBitmap != null) {
if (DEBUG) {
Log.v(TAG, "WallpaperTexture: release 0x" + getHash()
+ ", refCount=" + count);
}
mBitmap.recycle();
mBitmap = null;
}
}
}
private boolean isWcgContent() {
return mWcgContent;
}
private String getHash() {
return mBitmap != null ? Integer.toHexString(mBitmap.hashCode()) : "null";
}
private Rect getTextureDimensions() {
if (!mTextureUsed) {
mDimensions.set(mWallpaperManager.peekBitmapDimensions());
}
return mDimensions;
}
@Override
public String toString() {
return "{" + getHash() + ", " + mRefCount.get() + "}";
}
}
}

View File

@@ -19,15 +19,13 @@ package com.android.systemui.wallpapers;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -36,19 +34,13 @@ import static org.mockito.hamcrest.MockitoHamcrest.intThat;
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.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Handler;
import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.WindowManager;
@@ -57,28 +49,21 @@ import android.view.WindowMetrics;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.wallpapers.gl.ImageWallpaperRenderer;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.concurrent.CountDownLatch;
@SmallTest
@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;
@@ -99,19 +84,9 @@ public class ImageWallpaperTest extends SysuiTestCase {
@Mock
private Bitmap mWallpaperBitmap;
private int mBitmapWidth = 1;
private int mBitmapHeight = 1;
@Mock
private Handler mHandler;
@Mock
private FeatureFlags mFeatureFlags;
FakeSystemClock mFakeSystemClock = new FakeSystemClock();
FakeExecutor mFakeBackgroundExecutor = new FakeExecutor(mFakeSystemClock);
private CountDownLatch mEventCountdown;
@Before
public void setUp() throws Exception {
allowTestableLooperAsMainThread();
@@ -131,12 +106,8 @@ public class ImageWallpaperTest extends SysuiTestCase {
// set up bitmap
when(mWallpaperBitmap.getColorSpace()).thenReturn(ColorSpace.get(ColorSpace.Named.SRGB));
when(mWallpaperBitmap.getConfig()).thenReturn(Bitmap.Config.ARGB_8888);
when(mWallpaperBitmap.getWidth()).thenReturn(mBitmapWidth);
when(mWallpaperBitmap.getHeight()).thenReturn(mBitmapHeight);
// set up wallpaper manager
when(mWallpaperManager.peekBitmapDimensions())
.thenReturn(new Rect(0, 0, mBitmapWidth, mBitmapHeight));
when(mWallpaperManager.getBitmapAsUser(eq(UserHandle.USER_CURRENT), anyBoolean()))
.thenReturn(mWallpaperBitmap);
when(mMockContext.getSystemService(WallpaperManager.class)).thenReturn(mWallpaperManager);
@@ -144,104 +115,62 @@ public class ImageWallpaperTest extends SysuiTestCase {
// set up surface
when(mSurfaceHolder.getSurface()).thenReturn(mSurface);
doNothing().when(mSurface).hwuiDestroy();
// TODO remove code below. Outdated, used in only in old GL tests (that are ignored)
Resources resources = mock(Resources.class);
when(resources.getConfiguration()).thenReturn(mock(Configuration.class));
when(mMockContext.getResources()).thenReturn(resources);
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));
}
private void setBitmapDimensions(int bitmapWidth, int bitmapHeight) {
mBitmapWidth = bitmapWidth;
mBitmapHeight = bitmapHeight;
}
private ImageWallpaper createImageWallpaper() {
return new ImageWallpaper(mFeatureFlags, mFakeBackgroundExecutor) {
@Override
public Engine onCreateEngine() {
return new GLEngine(mHandler) {
@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();
}
};
}
};
}
@Test
@Ignore
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.
verifySurfaceSize(DISPLAY_WIDTH /* bmpWidth */,
DISPLAY_WIDTH /* bmpHeight */,
DISPLAY_WIDTH /* surfaceWidth */,
DISPLAY_WIDTH /* surfaceHeight */);
int bitmapSide = DISPLAY_WIDTH;
testSurfaceHelper(
bitmapSide /* bitmapWidth */,
bitmapSide /* bitmapHeight */,
bitmapSide /* expectedSurfaceWidth */,
bitmapSide /* expectedSurfaceHeight */);
}
@Test
@Ignore
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.
verifySurfaceSize(LOW_BMP_WIDTH /* bmpWidth */,
LOW_BMP_HEIGHT /* bmpHeight */,
LOW_BMP_WIDTH /* surfaceWidth */,
LOW_BMP_HEIGHT /* surfaceHeight */);
testSurfaceHelper(LOW_BMP_WIDTH /* bitmapWidth */,
LOW_BMP_HEIGHT /* bitmapHeight */,
LOW_BMP_WIDTH /* expectedSurfaceWidth */,
LOW_BMP_HEIGHT /* expectedSurfaceHeight */);
}
@Test
@Ignore
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.
verifySurfaceSize(INVALID_BMP_WIDTH /* bmpWidth */,
INVALID_BMP_HEIGHT /* bmpHeight */,
ImageWallpaper.GLEngine.MIN_SURFACE_WIDTH /* surfaceWidth */,
ImageWallpaper.GLEngine.MIN_SURFACE_HEIGHT /* surfaceHeight */);
// test that the surface is always at least MIN_SURFACE_WIDTH x MIN_SURFACE_HEIGHT
testMinSurfaceHelper(8, 8);
testMinSurfaceHelper(100, 2000);
testMinSurfaceHelper(200, 1);
}
private void verifySurfaceSize(int bmpWidth, int bmpHeight,
int surfaceWidth, int surfaceHeight) {
ImageWallpaper.GLEngine wallpaperEngine =
(ImageWallpaper.GLEngine) createImageWallpaper().onCreateEngine();
@Test
public void testLoadDrawAndUnloadBitmap() {
setBitmapDimensions(LOW_BMP_WIDTH, LOW_BMP_HEIGHT);
ImageWallpaper.GLEngine engineSpy = spy(wallpaperEngine);
ImageWallpaper.CanvasEngine spyEngine = getSpyEngine();
spyEngine.onCreate(mSurfaceHolder);
spyEngine.onSurfaceRedrawNeeded(mSurfaceHolder);
assertThat(mFakeBackgroundExecutor.numPending()).isAtLeast(1);
setBitmapDimensions(bmpWidth, bmpHeight);
int n = 0;
while (mFakeBackgroundExecutor.numPending() >= 1) {
n++;
assertThat(n).isAtMost(10);
mFakeBackgroundExecutor.runNextReady();
mFakeSystemClock.advanceTime(1000);
}
ImageWallpaperRenderer renderer = new ImageWallpaperRenderer(mMockContext);
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);
verify(spyEngine, times(1)).drawFrameOnCanvas(mWallpaperBitmap);
assertThat(spyEngine.isBitmapLoaded()).isFalse();
}
private ImageWallpaper createImageWallpaperCanvas() {
return new ImageWallpaper(mFeatureFlags, mFakeBackgroundExecutor) {
private ImageWallpaper createImageWallpaper() {
return new ImageWallpaper(mFakeBackgroundExecutor) {
@Override
public Engine onCreateEngine() {
return new CanvasEngine() {
@@ -267,7 +196,7 @@ public class ImageWallpaperTest extends SysuiTestCase {
}
private ImageWallpaper.CanvasEngine getSpyEngine() {
ImageWallpaper imageWallpaper = createImageWallpaperCanvas();
ImageWallpaper imageWallpaper = createImageWallpaper();
ImageWallpaper.CanvasEngine engine =
(ImageWallpaper.CanvasEngine) imageWallpaper.onCreateEngine();
ImageWallpaper.CanvasEngine spyEngine = spy(engine);
@@ -280,48 +209,32 @@ public class ImageWallpaperTest extends SysuiTestCase {
return spyEngine;
}
@Test
public void testMinSurface() {
// test that the surface is always at least MIN_SURFACE_WIDTH x MIN_SURFACE_HEIGHT
testMinSurfaceHelper(8, 8);
testMinSurfaceHelper(100, 2000);
testMinSurfaceHelper(200, 1);
private void setBitmapDimensions(int bitmapWidth, int bitmapHeight) {
when(mWallpaperManager.peekBitmapDimensions())
.thenReturn(new Rect(0, 0, bitmapWidth, bitmapHeight));
when(mWallpaperBitmap.getWidth()).thenReturn(bitmapWidth);
when(mWallpaperBitmap.getHeight()).thenReturn(bitmapHeight);
}
private void testMinSurfaceHelper(int bitmapWidth, int bitmapHeight) {
testSurfaceHelper(bitmapWidth, bitmapHeight,
Math.max(ImageWallpaper.CanvasEngine.MIN_SURFACE_WIDTH, bitmapWidth),
Math.max(ImageWallpaper.CanvasEngine.MIN_SURFACE_HEIGHT, bitmapHeight));
}
private void testSurfaceHelper(int bitmapWidth, int bitmapHeight,
int expectedSurfaceWidth, int expectedSurfaceHeight) {
clearInvocations(mSurfaceHolder);
setBitmapDimensions(bitmapWidth, bitmapHeight);
ImageWallpaper imageWallpaper = createImageWallpaperCanvas();
ImageWallpaper imageWallpaper = createImageWallpaper();
ImageWallpaper.CanvasEngine engine =
(ImageWallpaper.CanvasEngine) imageWallpaper.onCreateEngine();
engine.onCreate(mSurfaceHolder);
verify(mSurfaceHolder, times(1)).setFixedSize(
intThat(greaterThanOrEqualTo(ImageWallpaper.CanvasEngine.MIN_SURFACE_WIDTH)),
intThat(greaterThanOrEqualTo(ImageWallpaper.CanvasEngine.MIN_SURFACE_HEIGHT)));
}
@Test
public void testLoadDrawAndUnloadBitmap() {
setBitmapDimensions(LOW_BMP_WIDTH, LOW_BMP_HEIGHT);
ImageWallpaper.CanvasEngine spyEngine = getSpyEngine();
spyEngine.onCreate(mSurfaceHolder);
spyEngine.onSurfaceRedrawNeeded(mSurfaceHolder);
assertThat(mFakeBackgroundExecutor.numPending()).isAtLeast(1);
int n = 0;
while (mFakeBackgroundExecutor.numPending() >= 1) {
n++;
assertThat(n).isAtMost(10);
mFakeBackgroundExecutor.runNextReady();
mFakeSystemClock.advanceTime(1000);
}
verify(spyEngine, times(1)).drawFrameOnCanvas(mWallpaperBitmap);
assertThat(spyEngine.isBitmapLoaded()).isFalse();
intThat(equalTo(expectedSurfaceWidth)),
intThat(equalTo(expectedSurfaceHeight)));
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.systemui.wallpapers.canvas;
package com.android.systemui.wallpapers;
import static com.google.common.truth.Truth.assertThat;

View File

@@ -1,128 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.wallpapers.gl;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.graphics.PixelFormat;
import android.testing.AndroidTestingRunner;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceHolder;
import android.view.SurfaceSession;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@Ignore
public class EglHelperTest extends SysuiTestCase {
@Spy
private EglHelper mEglHelper;
@Mock
private SurfaceHolder mSurfaceHolder;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
prepareSurface();
}
@After
public void tearDown() {
mSurfaceHolder.getSurface().destroy();
mSurfaceHolder = null;
}
private void prepareSurface() {
final SurfaceSession session = new SurfaceSession();
final SurfaceControl control = new SurfaceControl.Builder(session)
.setName("Test")
.setBufferSize(100, 100)
.setFormat(PixelFormat.RGB_888)
.build();
final Surface surface = new Surface();
surface.copyFrom(control);
when(mSurfaceHolder.getSurface()).thenReturn(surface);
assertThat(mSurfaceHolder.getSurface()).isNotNull();
assertThat(mSurfaceHolder.getSurface().isValid()).isTrue();
}
@Test
public void testInit_normal() {
mEglHelper.init(mSurfaceHolder, false /* wideColorGamut */);
assertThat(mEglHelper.hasEglDisplay()).isTrue();
assertThat(mEglHelper.hasEglContext()).isTrue();
assertThat(mEglHelper.hasEglSurface()).isTrue();
verify(mEglHelper).askCreatingEglWindowSurface(
any(SurfaceHolder.class), eq(null), anyInt());
}
@Test
public void testInit_wide_gamut() {
// In EglHelper, EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT = 0x3490;
doReturn(0x3490).when(mEglHelper).getWcgCapability();
// In EglHelper, KHR_GL_COLOR_SPACE = "EGL_KHR_gl_colorspace";
doReturn(true).when(mEglHelper).checkExtensionCapability("EGL_KHR_gl_colorspace");
ArgumentCaptor<int[]> ac = ArgumentCaptor.forClass(int[].class);
// {EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT, EGL_NONE}
final int[] expectedArgument = new int[] {0x309D, 0x3490, 0x3038};
mEglHelper.init(mSurfaceHolder, true /* wideColorGamut */);
verify(mEglHelper)
.askCreatingEglWindowSurface(any(SurfaceHolder.class), ac.capture(), anyInt());
assertThat(ac.getValue()).isNotNull();
assertThat(ac.getValue()).isEqualTo(expectedArgument);
}
@Test
@Ignore
public void testFinish_shouldNotCrash() {
mEglHelper.terminateEglDisplay();
assertThat(mEglHelper.hasEglDisplay()).isFalse();
assertThat(mEglHelper.hasEglSurface()).isFalse();
assertThat(mEglHelper.hasEglContext()).isFalse();
mEglHelper.finish();
verify(mEglHelper, never()).destroyEglContext();
verify(mEglHelper, never()).destroyEglSurface();
verify(mEglHelper, atMost(1)).terminateEglDisplay();
}
}

View File

@@ -1,89 +0,0 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.wallpapers.gl;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import android.app.WallpaperManager;
import android.app.WallpaperManager.ColorManagementProxy;
import android.graphics.Bitmap;
import android.graphics.ColorSpace;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@Ignore
public class ImageWallpaperRendererTest extends SysuiTestCase {
private WallpaperManager mWpmSpy;
@Before
public void setUp() throws Exception {
final WallpaperManager wpm = mContext.getSystemService(WallpaperManager.class);
mWpmSpy = spy(wpm);
mContext.addMockSystemService(WallpaperManager.class, mWpmSpy);
}
@Test
public void testWcgContent() throws IOException {
final Bitmap srgbBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
final Bitmap p3Bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888,
false /* hasAlpha */, ColorSpace.get(ColorSpace.Named.DISPLAY_P3));
final ColorManagementProxy proxy = new ColorManagementProxy(mContext);
final ColorManagementProxy cmProxySpy = spy(proxy);
final Set<ColorSpace> supportedWideGamuts = new HashSet<>();
supportedWideGamuts.add(ColorSpace.get(ColorSpace.Named.DISPLAY_P3));
try {
doReturn(true).when(mWpmSpy).shouldEnableWideColorGamut();
doReturn(cmProxySpy).when(mWpmSpy).getColorManagementProxy();
doReturn(supportedWideGamuts).when(cmProxySpy).getSupportedColorSpaces();
mWpmSpy.setBitmap(p3Bitmap);
ImageWallpaperRenderer rendererP3 = new ImageWallpaperRenderer(mContext);
rendererP3.reportSurfaceSize();
assertThat(rendererP3.isWcgContent()).isTrue();
mWpmSpy.setBitmap(srgbBitmap);
ImageWallpaperRenderer renderer = new ImageWallpaperRenderer(mContext);
assertThat(renderer.isWcgContent()).isFalse();
} finally {
srgbBitmap.recycle();
p3Bitmap.recycle();
}
}
}