Merge "Validate wallpaper dimension while generating crop" into qt-dev

This commit is contained in:
TreeHugger Robot
2019-11-06 03:59:30 +00:00
committed by Android (Google) Code Review
2 changed files with 232 additions and 30 deletions

View File

@@ -0,0 +1,148 @@
/*
* 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.server.wallpaper;
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_GREEN_SIZE;
import static android.opengl.EGL14.EGL_HEIGHT;
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_WIDTH;
import static android.opengl.EGL14.eglChooseConfig;
import static android.opengl.EGL14.eglCreateContext;
import static android.opengl.EGL14.eglCreatePbufferSurface;
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.eglTerminate;
import static android.opengl.GLES20.GL_MAX_TEXTURE_SIZE;
import static android.opengl.GLES20.glGetIntegerv;
import android.opengl.EGLConfig;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLSurface;
import android.opengl.GLUtils;
import android.os.SystemProperties;
import android.util.Log;
class GLHelper {
private static final String TAG = GLHelper.class.getSimpleName();
private static final int sMaxTextureSize;
static {
int maxTextureSize = SystemProperties.getInt("sys.max_texture_size", 0);
sMaxTextureSize = maxTextureSize > 0 ? maxTextureSize : retrieveTextureSizeFromGL();
}
private static int retrieveTextureSizeFromGL() {
try {
String err;
// Before we can retrieve info from GL,
// we have to create EGLContext, EGLConfig and EGLDisplay first.
// We will fail at querying info from GL once one of above failed.
// When this happens, we will use defValue instead.
EGLDisplay eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (eglDisplay == null || eglDisplay == EGL_NO_DISPLAY) {
err = "eglGetDisplay failed: " + GLUtils.getEGLErrorString(eglGetError());
throw new RuntimeException(err);
}
if (!eglInitialize(eglDisplay, null, 0 /* majorOffset */, null, 1 /* minorOffset */)) {
err = "eglInitialize failed: " + GLUtils.getEGLErrorString(eglGetError());
throw new RuntimeException(err);
}
EGLConfig eglConfig = null;
int[] configsCount = new int[1];
EGLConfig[] configs = new EGLConfig[1];
int[] configSpec = new int[] {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
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_CONFIG_CAVEAT, EGL_NONE,
EGL_NONE
};
if (!eglChooseConfig(eglDisplay, configSpec, 0 /* attrib_listOffset */,
configs, 0 /* configOffset */, 1 /* config_size */,
configsCount, 0 /* num_configOffset */)) {
err = "eglChooseConfig failed: " + GLUtils.getEGLErrorString(eglGetError());
throw new RuntimeException(err);
} else if (configsCount[0] > 0) {
eglConfig = configs[0];
}
if (eglConfig == null) {
throw new RuntimeException("eglConfig not initialized!");
}
int[] attr_list = new int[] {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
EGLContext eglContext = eglCreateContext(
eglDisplay, eglConfig, EGL_NO_CONTEXT, attr_list, 0 /* offset */);
if (eglContext == null || eglContext == EGL_NO_CONTEXT) {
err = "eglCreateContext failed: " + GLUtils.getEGLErrorString(eglGetError());
throw new RuntimeException(err);
}
// We create a push buffer temporarily for querying info from GL.
int[] attrs = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE};
EGLSurface eglSurface =
eglCreatePbufferSurface(eglDisplay, eglConfig, attrs, 0 /* offset */);
eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
// Now, we are ready to query the info from GL.
int[] maxSize = new int[1];
glGetIntegerv(GL_MAX_TEXTURE_SIZE, maxSize, 0 /* offset */);
// We have got the info we want, release all egl resources.
eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroySurface(eglDisplay, eglSurface);
eglDestroyContext(eglDisplay, eglContext);
eglTerminate(eglDisplay);
return maxSize[0];
} catch (RuntimeException e) {
Log.w(TAG, "Retrieve from GL failed", e);
return Integer.MAX_VALUE;
}
}
static int getMaxTextureSize() {
return sMaxTextureSize;
}
}

View File

@@ -134,6 +134,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
private static final boolean DEBUG = false;
private static final boolean DEBUG_LIVE = true;
// This 100MB limitation is defined in RecordingCanvas.
private static final int MAX_BITMAP_SIZE = 100 * 1024 * 1024;
public static class Lifecycle extends SystemService {
private IWallpaperManagerService mService;
@@ -572,7 +575,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
// Only generate crop for default display.
final DisplayData wpData = getDisplayDataOrCreate(DEFAULT_DISPLAY);
Rect cropHint = new Rect(wallpaper.cropHint);
final Rect cropHint = new Rect(wallpaper.cropHint);
final DisplayInfo displayInfo = new DisplayInfo();
mDisplayManager.getDisplay(DEFAULT_DISPLAY).getDisplayInfo(displayInfo);
if (DEBUG) {
Slog.v(TAG, "Generating crop for new wallpaper(s): 0x"
@@ -618,12 +623,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
// scale if the crop height winds up not matching the recommended metrics
needScale = (wpData.mHeight != cropHint.height());
needScale = wpData.mHeight != cropHint.height()
|| cropHint.height() > GLHelper.getMaxTextureSize()
|| cropHint.width() > GLHelper.getMaxTextureSize();
//make sure screen aspect ratio is preserved if width is scaled under screen size
if (needScale) {
final DisplayInfo displayInfo = new DisplayInfo();
mDisplayManager.getDisplay(DEFAULT_DISPLAY).getDisplayInfo(displayInfo);
final float scaleByHeight = (float) wpData.mHeight / (float) cropHint.height();
final int newWidth = (int) (cropHint.width() * scaleByHeight);
if (newWidth < displayInfo.logicalWidth) {
@@ -644,14 +649,29 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if (!needCrop && !needScale) {
// Simple case: the nominal crop fits what we want, so we take
// the whole thing and just copy the image file directly.
if (DEBUG) {
Slog.v(TAG, "Null crop of new wallpaper; copying");
// TODO: It is not accurate to estimate bitmap size without decoding it,
// may be we can try to remove this optimized way in the future,
// that means, we will always go into the 'else' block.
// This is just a quick estimation, may be smaller than it is.
long estimateSize = options.outWidth * options.outHeight * 4;
// A bitmap over than MAX_BITMAP_SIZE will make drawBitmap() fail.
// Please see: RecordingCanvas#throwIfCannotDraw.
if (estimateSize < MAX_BITMAP_SIZE) {
success = FileUtils.copyFile(wallpaper.wallpaperFile, wallpaper.cropFile);
}
success = FileUtils.copyFile(wallpaper.wallpaperFile, wallpaper.cropFile);
if (!success) {
wallpaper.cropFile.delete();
// TODO: fall back to default wallpaper in this case
}
if (DEBUG) {
Slog.v(TAG, "Null crop of new wallpaper, estimate size="
+ estimateSize + ", success=" + success);
}
} else {
// Fancy case: crop and scale. First, we decode and scale down if appropriate.
FileOutputStream f = null;
@@ -665,49 +685,78 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
// We calculate the largest power-of-two under the actual ratio rather than
// just let the decode take care of it because we also want to remap where the
// cropHint rectangle lies in the decoded [super]rect.
final BitmapFactory.Options scaler;
final int actualScale = cropHint.height() / wpData.mHeight;
int scale = 1;
while (2*scale < actualScale) {
while (2 * scale <= actualScale) {
scale *= 2;
}
if (scale > 1) {
scaler = new BitmapFactory.Options();
scaler.inSampleSize = scale;
options.inSampleSize = scale;
options.inJustDecodeBounds = false;
final Rect estimateCrop = new Rect(cropHint);
estimateCrop.scale(1f / options.inSampleSize);
final float hRatio = (float) wpData.mHeight / estimateCrop.height();
final int destHeight = (int) (estimateCrop.height() * hRatio);
final int destWidth = (int) (estimateCrop.width() * hRatio);
// We estimated an invalid crop, try to adjust the cropHint to get a valid one.
if (destWidth > GLHelper.getMaxTextureSize()) {
int newHeight = (int) (wpData.mHeight / hRatio);
int newWidth = (int) (wpData.mWidth / hRatio);
if (DEBUG) {
Slog.v(TAG, "Downsampling cropped rect with scale " + scale);
Slog.v(TAG, "Invalid crop dimensions, trying to adjust.");
}
} else {
scaler = null;
estimateCrop.set(cropHint);
estimateCrop.left += (cropHint.width() - newWidth) / 2;
estimateCrop.top += (cropHint.height() - newHeight) / 2;
estimateCrop.right = estimateCrop.left + newWidth;
estimateCrop.bottom = estimateCrop.top + newHeight;
cropHint.set(estimateCrop);
estimateCrop.scale(1f / options.inSampleSize);
}
Bitmap cropped = decoder.decodeRegion(cropHint, scaler);
// We've got the safe cropHint; now we want to scale it properly to
// the desired rectangle.
// That's a height-biased operation: make it fit the hinted height.
final int safeHeight = (int) (estimateCrop.height() * hRatio);
final int safeWidth = (int) (estimateCrop.width() * hRatio);
if (DEBUG) {
Slog.v(TAG, "Decode parameters:");
Slog.v(TAG, " cropHint=" + cropHint + ", estimateCrop=" + estimateCrop);
Slog.v(TAG, " down sampling=" + options.inSampleSize
+ ", hRatio=" + hRatio);
Slog.v(TAG, " dest=" + destWidth + "x" + destHeight);
Slog.v(TAG, " safe=" + safeWidth + "x" + safeHeight);
Slog.v(TAG, " maxTextureSize=" + GLHelper.getMaxTextureSize());
}
Bitmap cropped = decoder.decodeRegion(cropHint, options);
decoder.recycle();
if (cropped == null) {
Slog.e(TAG, "Could not decode new wallpaper");
} else {
// We've got the extracted crop; now we want to scale it properly to
// the desired rectangle. That's a height-biased operation: make it
// fit the hinted height, and accept whatever width we end up with.
cropHint.offsetTo(0, 0);
cropHint.right /= scale; // adjust by downsampling factor
cropHint.bottom /= scale;
final float heightR =
((float) wpData.mHeight) / ((float) cropHint.height());
if (DEBUG) {
Slog.v(TAG, "scale " + heightR + ", extracting " + cropHint);
}
final int destWidth = (int)(cropHint.width() * heightR);
// We are safe to create final crop with safe dimensions now.
final Bitmap finalCrop = Bitmap.createScaledBitmap(cropped,
destWidth, wpData.mHeight, true);
safeWidth, safeHeight, true);
if (DEBUG) {
Slog.v(TAG, "Final extract:");
Slog.v(TAG, " dims: w=" + wpData.mWidth
+ " h=" + wpData.mHeight);
Slog.v(TAG, " out: w=" + finalCrop.getWidth()
Slog.v(TAG, " out: w=" + finalCrop.getWidth()
+ " h=" + finalCrop.getHeight());
}
// A bitmap over than MAX_BITMAP_SIZE will make drawBitmap() fail.
// Please see: RecordingCanvas#throwIfCannotDraw.
if (finalCrop.getByteCount() > MAX_BITMAP_SIZE) {
throw new RuntimeException(
"Too large bitmap, limit=" + MAX_BITMAP_SIZE);
}
f = new FileOutputStream(wallpaper.cropFile);
bos = new BufferedOutputStream(f, 32*1024);
finalCrop.compress(Bitmap.CompressFormat.JPEG, 100, bos);
@@ -1981,6 +2030,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if (!isWallpaperSupported(callingPackage)) {
return;
}
// Make sure both width and height are not larger than max texture size.
width = Math.min(width, GLHelper.getMaxTextureSize());
height = Math.min(height, GLHelper.getMaxTextureSize());
synchronized (mLock) {
int userId = UserHandle.getCallingUserId();
WallpaperData wallpaper = getWallpaperSafeLocked(userId, FLAG_SYSTEM);