Merge "Validate wallpaper dimension while generating crop" into qt-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
5d4aa134a9
148
services/core/java/com/android/server/wallpaper/GLHelper.java
Normal file
148
services/core/java/com/android/server/wallpaper/GLHelper.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user