Render ImageWallpaper with OpenGL ES and apply visual effects. (Fix bug)
We have to render image wallpaper with OpenGL ES to apply some amazing visual effects. Bug: 122803209 Bug: 124073420 Bug: 123616712 Bug: 123615467 Test: Manually. Change-Id: I0123d4ba2acb5a84b709c0468910e006c8e49563
This commit is contained in:
@@ -22,6 +22,11 @@
|
||||
android:sharedUserId="android.uid.systemui"
|
||||
coreApp="true">
|
||||
|
||||
<!-- Using OpenGL ES 2.0 -->
|
||||
<uses-feature
|
||||
android:glEsVersion="0x00020000"
|
||||
android:required="true" />
|
||||
|
||||
<!-- SysUI must be the one to define this permission; its name is
|
||||
referenced by the core OS. -->
|
||||
<permission android:name="android.permission.systemui.IDENTITY"
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
precision mediump float;
|
||||
|
||||
uniform sampler2D uTexture;
|
||||
uniform float uCenterReveal;
|
||||
uniform float uReveal;
|
||||
uniform float uAod2Opacity;
|
||||
varying vec2 vTextureCoordinates;
|
||||
|
||||
vec3 luminosity(vec3 color) {
|
||||
float lum = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
|
||||
return vec3(lum);
|
||||
}
|
||||
|
||||
vec4 transform(vec3 diffuse) {
|
||||
// TODO: Add well comments here, tracking on b/123615467.
|
||||
vec3 lum = luminosity(diffuse);
|
||||
diffuse = mix(diffuse, lum, smoothstep(0., uCenterReveal, uReveal));
|
||||
float val = mix(uReveal, uCenterReveal, step(uCenterReveal, uReveal));
|
||||
diffuse = smoothstep(val, 1.0, diffuse);
|
||||
diffuse *= uAod2Opacity * (1. - smoothstep(uCenterReveal, 1., uReveal));
|
||||
return vec4(diffuse.r, diffuse.g, diffuse.b, 1.);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 fragColor = texture2D(uTexture, vTextureCoordinates);
|
||||
gl_FragColor = transform(fragColor.rgb);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
attribute vec4 aPosition;
|
||||
attribute vec2 aTextureCoordinates;
|
||||
varying vec2 vTextureCoordinates;
|
||||
|
||||
void main() {
|
||||
vTextureCoordinates = aTextureCoordinates;
|
||||
gl_Position = aPosition;
|
||||
}
|
||||
@@ -28,7 +28,9 @@ import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Region.Op;
|
||||
import android.hardware.display.DisplayManager;
|
||||
import android.opengl.GLSurfaceView;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Trace;
|
||||
import android.service.wallpaper.WallpaperService;
|
||||
@@ -39,6 +41,7 @@ import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.systemui.glwallpaper.ImageWallpaperRenderer;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
@@ -73,10 +76,78 @@ public class ImageWallpaper extends WallpaperService {
|
||||
|
||||
@Override
|
||||
public Engine onCreateEngine() {
|
||||
mEngine = new DrawableEngine();
|
||||
return mEngine;
|
||||
if (Build.IS_DEBUGGABLE) {
|
||||
Log.v(TAG, "We are using GLEngine");
|
||||
}
|
||||
return new GLEngine(this);
|
||||
}
|
||||
|
||||
class GLEngine extends Engine {
|
||||
private GLWallpaperSurfaceView mWallpaperSurfaceView;
|
||||
|
||||
GLEngine(Context context) {
|
||||
mWallpaperSurfaceView = new GLWallpaperSurfaceView(context);
|
||||
mWallpaperSurfaceView.setRenderer(
|
||||
new ImageWallpaperRenderer(context, mWallpaperSurfaceView));
|
||||
mWallpaperSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
|
||||
setOffsetNotificationsEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAmbientModeChanged(boolean inAmbientMode, long animationDuration) {
|
||||
if (mWallpaperSurfaceView != null) {
|
||||
mWallpaperSurfaceView.notifyAmbientModeChanged(inAmbientMode, animationDuration);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOffsetsChanged(float xOffset, float yOffset, float xOffsetStep,
|
||||
float yOffsetStep, int xPixelOffset, int yPixelOffset) {
|
||||
if (mWallpaperSurfaceView != null) {
|
||||
mWallpaperSurfaceView.notifyOffsetsChanged(xOffset, yOffset);
|
||||
}
|
||||
}
|
||||
|
||||
private class GLWallpaperSurfaceView extends GLSurfaceView implements ImageGLView {
|
||||
private WallpaperStatusListener mWallpaperChangedListener;
|
||||
|
||||
GLWallpaperSurfaceView(Context context) {
|
||||
super(context);
|
||||
setEGLContextClientVersion(2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SurfaceHolder getHolder() {
|
||||
return getSurfaceHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRenderer(Renderer renderer) {
|
||||
super.setRenderer(renderer);
|
||||
mWallpaperChangedListener = (WallpaperStatusListener) renderer;
|
||||
}
|
||||
|
||||
private void notifyAmbientModeChanged(boolean inAmbient, long duration) {
|
||||
if (mWallpaperChangedListener != null) {
|
||||
mWallpaperChangedListener.onAmbientModeChanged(inAmbient, duration);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyOffsetsChanged(float xOffset, float yOffset) {
|
||||
if (mWallpaperChangedListener != null) {
|
||||
mWallpaperChangedListener.onOffsetsChanged(
|
||||
xOffset, yOffset, getHolder().getSurfaceFrame());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render() {
|
||||
requestRender();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remove this engine, tracking on b/123617158.
|
||||
class DrawableEngine extends Engine {
|
||||
private final Runnable mUnloadWallpaperCallback = () -> {
|
||||
unloadWallpaper(false /* forgetSize */);
|
||||
@@ -564,4 +635,35 @@ public class ImageWallpaper extends WallpaperService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A listener to trace status of image wallpaper.
|
||||
*/
|
||||
public interface WallpaperStatusListener {
|
||||
|
||||
/**
|
||||
* Called back while ambient mode changes.
|
||||
* @param inAmbientMode true if is in ambient mode, false otherwise.
|
||||
* @param duration the duration of animation.
|
||||
*/
|
||||
void onAmbientModeChanged(boolean inAmbientMode, long duration);
|
||||
|
||||
/**
|
||||
* Called back while wallpaper offsets.
|
||||
* @param xOffset The offset portion along x.
|
||||
* @param yOffset The offset portion along y.
|
||||
*/
|
||||
void onOffsetsChanged(float xOffset, float yOffset, Rect frame);
|
||||
}
|
||||
|
||||
/**
|
||||
* An abstraction for view of GLRenderer.
|
||||
*/
|
||||
public interface ImageGLView {
|
||||
|
||||
/**
|
||||
* Ask the view to render.
|
||||
*/
|
||||
void render();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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.glwallpaper;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* 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.glwallpaper;
|
||||
|
||||
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.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
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();
|
||||
|
||||
static final String A_POSITION = "aPosition";
|
||||
static final String A_TEXTURE_COORDINATES = "aTextureCoordinates";
|
||||
static final String U_CENTER_REVEAL = "uCenterReveal";
|
||||
static final String U_REVEAL = "uReveal";
|
||||
static final String U_AOD2OPACITY = "uAod2Opacity";
|
||||
static final String U_TEXTURE = "uTexture";
|
||||
|
||||
private static final int HANDLE_UNDEFINED = -1;
|
||||
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 mUniAod2Opacity;
|
||||
private int mUniCenterReveal;
|
||||
private int mUniReveal;
|
||||
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() {
|
||||
setupAttributes();
|
||||
setupUniforms();
|
||||
}
|
||||
|
||||
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() {
|
||||
mUniAod2Opacity = mProgram.getUniformHandle(U_AOD2OPACITY);
|
||||
mUniCenterReveal = mProgram.getUniformHandle(U_CENTER_REVEAL);
|
||||
mUniReveal = mProgram.getUniformHandle(U_REVEAL);
|
||||
mUniTexture = mProgram.getUniformHandle(U_TEXTURE);
|
||||
}
|
||||
|
||||
int getHandle(String name) {
|
||||
switch (name) {
|
||||
case A_POSITION:
|
||||
return mAttrPosition;
|
||||
case A_TEXTURE_COORDINATES:
|
||||
return mAttrTextureCoordinates;
|
||||
case U_AOD2OPACITY:
|
||||
return mUniAod2Opacity;
|
||||
case U_CENTER_REVEAL:
|
||||
return mUniCenterReveal;
|
||||
case U_REVEAL:
|
||||
return mUniReveal;
|
||||
case U_TEXTURE:
|
||||
return mUniTexture;
|
||||
default:
|
||||
return HANDLE_UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
void draw() {
|
||||
glDrawArrays(GL_TRIANGLES, 0, VERTICES.length / 2);
|
||||
}
|
||||
|
||||
void setupTexture(Bitmap bitmap) {
|
||||
final int[] tids = new int[1];
|
||||
|
||||
if (bitmap == null) {
|
||||
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;
|
||||
}
|
||||
|
||||
// Bind a named texture to a texturing 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];
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void adjustTextureCoordinates(Bitmap bitmap, int surfaceWidth, int surfaceHeight,
|
||||
float xOffset, float yOffset) {
|
||||
if (bitmap == null) {
|
||||
Log.d(TAG, "adjustTextureCoordinates: invalid bitmap");
|
||||
return;
|
||||
}
|
||||
|
||||
int bitmapWidth = bitmap.getWidth();
|
||||
int bitmapHeight = bitmap.getHeight();
|
||||
float ratioW = 1f;
|
||||
float ratioH = 1f;
|
||||
float rX = 0f;
|
||||
float rY = 0f;
|
||||
float[] coordinates = null;
|
||||
|
||||
final boolean adjustWidth = bitmapWidth > surfaceWidth;
|
||||
final boolean adjustHeight = bitmapHeight > surfaceHeight;
|
||||
|
||||
if (adjustWidth || adjustHeight) {
|
||||
coordinates = TEXTURES.clone();
|
||||
}
|
||||
|
||||
if (adjustWidth) {
|
||||
float x = (float) Math.round((bitmapWidth - surfaceWidth) * xOffset) / bitmapWidth;
|
||||
ratioW = (float) surfaceWidth / bitmapWidth;
|
||||
float referenceX = x + ratioW > 1f ? 1f - ratioW : x;
|
||||
for (int i = 0; i < coordinates.length; i += 2) {
|
||||
if (i == 2 || i == 4 || i == 6) {
|
||||
coordinates[i] = Math.min(1f, referenceX + ratioW);
|
||||
} else {
|
||||
coordinates[i] = referenceX;
|
||||
}
|
||||
}
|
||||
rX = referenceX;
|
||||
}
|
||||
|
||||
|
||||
if (adjustHeight) {
|
||||
float y = (float) Math.round((bitmapHeight - surfaceHeight) * yOffset) / bitmapHeight;
|
||||
ratioH = (float) surfaceHeight / bitmapHeight;
|
||||
float referenceY = y + ratioH > 1f ? 1f - ratioH : y;
|
||||
for (int i = 1; i < coordinates.length; i += 2) {
|
||||
if (i == 1 || i == 3 || i == 11) {
|
||||
coordinates[i] = Math.min(1f, referenceY + ratioH);
|
||||
} else {
|
||||
coordinates[i] = referenceY;
|
||||
}
|
||||
}
|
||||
rY = referenceY;
|
||||
}
|
||||
|
||||
if (adjustWidth || adjustHeight) {
|
||||
if (Build.IS_DEBUGGABLE) {
|
||||
Log.d(TAG, "adjustTextureCoordinates: sW=" + surfaceWidth + ", sH=" + surfaceHeight
|
||||
+ ", bW=" + bitmapWidth + ", bH=" + bitmapHeight
|
||||
+ ", rW=" + ratioW + ", rH=" + ratioH + ", rX=" + rX + ", rY=" + rY);
|
||||
}
|
||||
mTextureBuffer.put(coordinates);
|
||||
mTextureBuffer.position(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* 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.glwallpaper;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ColorMatrix;
|
||||
import android.graphics.ColorMatrixColorFilter;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Handler;
|
||||
import android.os.Handler.Callback;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* A helper class that computes histogram and percentile 85 from a bitmap.
|
||||
* Percentile 85 will be computed each time the user picks a new image wallpaper.
|
||||
*/
|
||||
class ImageProcessHelper {
|
||||
private static final String TAG = ImageProcessHelper.class.getSimpleName();
|
||||
private static final float DEFAULT_PER85 = 0.8f;
|
||||
private static final int MSG_UPDATE_PER85 = 1;
|
||||
|
||||
/**
|
||||
* This color matrix will be applied to each pixel to get luminance from rgb by below formula:
|
||||
* Luminance = .2126f * r + .7152f * g + .0722f * b.
|
||||
*/
|
||||
private static final float[] LUMINOSITY_MATRIX = new float[] {
|
||||
.2126f, .0000f, .0000f, .0000f, .0000f,
|
||||
.0000f, .7152f, .0000f, .0000f, .0000f,
|
||||
.0000f, .0000f, .0722f, .0000f, .0000f,
|
||||
.0000f, .0000f, .0000f, 1.000f, .0000f
|
||||
};
|
||||
|
||||
private final Handler mHandler = new Handler(new Callback() {
|
||||
@Override
|
||||
public boolean handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_UPDATE_PER85:
|
||||
mPer85 = (float) msg.obj;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
private float mPer85 = DEFAULT_PER85;
|
||||
|
||||
void startComputingPercentile85(Bitmap bitmap) {
|
||||
new Per85ComputeTask(mHandler).execute(bitmap);
|
||||
}
|
||||
|
||||
float getPercentile85() {
|
||||
return mPer85;
|
||||
}
|
||||
|
||||
private static class Per85ComputeTask extends AsyncTask<Bitmap, Void, Float> {
|
||||
private Handler mUpdateHandler;
|
||||
|
||||
Per85ComputeTask(Handler handler) {
|
||||
super(handler);
|
||||
mUpdateHandler = handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Float doInBackground(Bitmap... bitmaps) {
|
||||
Bitmap bitmap = bitmaps[0];
|
||||
if (bitmap != null) {
|
||||
int[] histogram = processHistogram(bitmap);
|
||||
return computePercentile85(bitmap, histogram);
|
||||
}
|
||||
Log.e(TAG, "Per85ComputeTask: Can't get bitmap");
|
||||
return DEFAULT_PER85;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Float result) {
|
||||
Message msg = mUpdateHandler.obtainMessage(MSG_UPDATE_PER85, result);
|
||||
mUpdateHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
private int[] processHistogram(Bitmap bitmap) {
|
||||
int width = bitmap.getWidth();
|
||||
int height = bitmap.getHeight();
|
||||
|
||||
Bitmap target = Bitmap.createBitmap(width, height, bitmap.getConfig());
|
||||
Canvas canvas = new Canvas(target);
|
||||
ColorMatrix cm = new ColorMatrix(LUMINOSITY_MATRIX);
|
||||
Paint paint = new Paint();
|
||||
paint.setColorFilter(new ColorMatrixColorFilter(cm));
|
||||
canvas.drawBitmap(bitmap, new Matrix(), paint);
|
||||
|
||||
// TODO: Fine tune the performance here, tracking on b/123615079.
|
||||
int[] histogram = new int[256];
|
||||
for (int row = 0; row < height; row++) {
|
||||
for (int col = 0; col < width; col++) {
|
||||
int pixel = target.getPixel(col, row);
|
||||
int y = Color.red(pixel) + Color.green(pixel) + Color.blue(pixel);
|
||||
histogram[y]++;
|
||||
}
|
||||
}
|
||||
|
||||
return histogram;
|
||||
}
|
||||
|
||||
private float computePercentile85(Bitmap bitmap, int[] histogram) {
|
||||
float per85 = DEFAULT_PER85;
|
||||
int pixelCount = bitmap.getWidth() * bitmap.getHeight();
|
||||
float[] acc = new float[256];
|
||||
for (int i = 0; i < acc.length; i++) {
|
||||
acc[i] = (float) histogram[i] / pixelCount;
|
||||
float prev = i == 0 ? 0f : acc[i - 1];
|
||||
float next = acc[i];
|
||||
float idx = (float) (i + 1) / 255;
|
||||
float sum = prev + next;
|
||||
if (prev < 0.85f && sum >= 0.85f) {
|
||||
per85 = idx;
|
||||
}
|
||||
if (i > 0) {
|
||||
acc[i] += acc[i - 1];
|
||||
}
|
||||
}
|
||||
return per85;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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.glwallpaper;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
|
||||
import com.android.systemui.Interpolators;
|
||||
|
||||
/**
|
||||
* Use ValueAnimator and appropriate interpolator to control the progress of reveal transition.
|
||||
* The transition will happen while getting awake and quit events.
|
||||
*/
|
||||
class ImageRevealHelper {
|
||||
private static final String TAG = ImageRevealHelper.class.getSimpleName();
|
||||
private static final float MAX_REVEAL = 0f;
|
||||
private static final float MIN_REVEAL = 1f;
|
||||
|
||||
private final ValueAnimator mAnimator;
|
||||
private final RevealStateListener mRevealListener;
|
||||
private float mReveal = MAX_REVEAL;
|
||||
private boolean mAwake = false;
|
||||
|
||||
ImageRevealHelper(RevealStateListener listener) {
|
||||
mRevealListener = listener;
|
||||
mAnimator = ValueAnimator.ofFloat();
|
||||
mAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
|
||||
mAnimator.addUpdateListener(animator -> {
|
||||
mReveal = (float) animator.getAnimatedValue();
|
||||
if (mRevealListener != null) {
|
||||
mRevealListener.onRevealStateChanged();
|
||||
}
|
||||
});
|
||||
mAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
private boolean mIsCanceled;
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
mIsCanceled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
if (!mIsCanceled) {
|
||||
mAwake = !mAwake;
|
||||
}
|
||||
mIsCanceled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void animate() {
|
||||
mAnimator.cancel();
|
||||
mAnimator.setFloatValues(mReveal, !mAwake ? MIN_REVEAL : MAX_REVEAL);
|
||||
mAnimator.start();
|
||||
}
|
||||
|
||||
public float getReveal() {
|
||||
return mReveal;
|
||||
}
|
||||
|
||||
public boolean isAwake() {
|
||||
return mAwake;
|
||||
}
|
||||
|
||||
void updateAwake(boolean awake, long duration) {
|
||||
mAwake = awake;
|
||||
mAnimator.setDuration(duration);
|
||||
animate();
|
||||
}
|
||||
|
||||
/**
|
||||
* A listener to trace value changes of reveal.
|
||||
*/
|
||||
public interface RevealStateListener {
|
||||
|
||||
/**
|
||||
* Called back while reveal status changes.
|
||||
*/
|
||||
void onRevealStateChanged();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* 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.glwallpaper;
|
||||
|
||||
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.glUniform1f;
|
||||
import static android.opengl.GLES20.glViewport;
|
||||
|
||||
import android.app.WallpaperManager;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Rect;
|
||||
import android.opengl.GLSurfaceView;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.systemui.ImageWallpaper;
|
||||
import com.android.systemui.ImageWallpaper.ImageGLView;
|
||||
import com.android.systemui.R;
|
||||
|
||||
import javax.microedition.khronos.egl.EGLConfig;
|
||||
import javax.microedition.khronos.opengles.GL10;
|
||||
|
||||
/**
|
||||
* A GL renderer for image wallpaper.
|
||||
*/
|
||||
public class ImageWallpaperRenderer implements GLSurfaceView.Renderer,
|
||||
ImageWallpaper.WallpaperStatusListener, ImageRevealHelper.RevealStateListener {
|
||||
private static final String TAG = ImageWallpaperRenderer.class.getSimpleName();
|
||||
|
||||
private final WallpaperManager mWallpaperManager;
|
||||
private final ImageGLProgram mProgram;
|
||||
private final ImageGLWallpaper mWallpaper;
|
||||
private final ImageProcessHelper mImageProcessHelper;
|
||||
private final ImageRevealHelper mImageRevealHelper;
|
||||
private final ImageGLView mGLView;
|
||||
private float mXOffset = 0f;
|
||||
private float mYOffset = 0f;
|
||||
|
||||
public ImageWallpaperRenderer(Context context, ImageGLView glView) {
|
||||
mWallpaperManager = context.getSystemService(WallpaperManager.class);
|
||||
if (mWallpaperManager == null) {
|
||||
Log.w(TAG, "WallpaperManager not available");
|
||||
}
|
||||
|
||||
mProgram = new ImageGLProgram(context);
|
||||
mWallpaper = new ImageGLWallpaper(mProgram);
|
||||
mImageProcessHelper = new ImageProcessHelper();
|
||||
mImageRevealHelper = new ImageRevealHelper(this);
|
||||
mGLView = glView;
|
||||
|
||||
if (mWallpaperManager != null) {
|
||||
// Compute per85 as transition threshold, this is an async work.
|
||||
mImageProcessHelper.startComputingPercentile85(mWallpaperManager.getBitmap());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
|
||||
glClearColor(0f, 0f, 0f, 1.0f);
|
||||
mProgram.useGLProgram(
|
||||
R.raw.image_wallpaper_vertex_shader, R.raw.image_wallpaper_fragment_shader);
|
||||
mWallpaper.setup();
|
||||
mWallpaper.setupTexture(mWallpaperManager.getBitmap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceChanged(GL10 gl, int width, int height) {
|
||||
glViewport(0, 0, width, height);
|
||||
if (Build.IS_DEBUGGABLE) {
|
||||
Log.d(TAG, "onSurfaceChanged: width=" + width + ", height=" + height
|
||||
+ ", xOffset=" + mXOffset + ", yOffset=" + mYOffset);
|
||||
}
|
||||
mWallpaper.adjustTextureCoordinates(mWallpaperManager.getBitmap(),
|
||||
width, height, mXOffset, mYOffset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawFrame(GL10 gl) {
|
||||
float threshold = mImageProcessHelper.getPercentile85();
|
||||
float reveal = mImageRevealHelper.getReveal();
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
glUniform1f(mWallpaper.getHandle(ImageGLWallpaper.U_AOD2OPACITY), 1);
|
||||
glUniform1f(mWallpaper.getHandle(ImageGLWallpaper.U_CENTER_REVEAL), threshold);
|
||||
glUniform1f(mWallpaper.getHandle(ImageGLWallpaper.U_REVEAL), reveal);
|
||||
|
||||
mWallpaper.useTexture();
|
||||
mWallpaper.draw();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAmbientModeChanged(boolean inAmbientMode, long duration) {
|
||||
mImageRevealHelper.updateAwake(!inAmbientMode, duration);
|
||||
requestRender();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOffsetsChanged(float xOffset, float yOffset, Rect frame) {
|
||||
if (frame == null || mWallpaperManager == null
|
||||
|| (xOffset == mXOffset && yOffset == mYOffset)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Bitmap bitmap = mWallpaperManager.getBitmap();
|
||||
if (bitmap == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int width = frame.width();
|
||||
int height = frame.height();
|
||||
mXOffset = xOffset;
|
||||
mYOffset = yOffset;
|
||||
|
||||
if (Build.IS_DEBUGGABLE) {
|
||||
Log.d(TAG, "onOffsetsChanged: width=" + width + ", height=" + height
|
||||
+ ", xOffset=" + mXOffset + ", yOffset=" + mYOffset);
|
||||
}
|
||||
mWallpaper.adjustTextureCoordinates(bitmap, width, height, mXOffset, mYOffset);
|
||||
requestRender();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRevealStateChanged() {
|
||||
requestRender();
|
||||
}
|
||||
|
||||
private void requestRender() {
|
||||
if (mGLView != null) {
|
||||
mGLView.render();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -479,8 +479,7 @@ public class StatusBar extends SystemUI implements DemoMode,
|
||||
updateAodMaskVisibility(deviceSupportsAodWallpaper && aodImageWallpaperEnabled);
|
||||
// If WallpaperInfo is null, it must be ImageWallpaper.
|
||||
final boolean supportsAmbientMode = deviceSupportsAodWallpaper
|
||||
&& (info == null && aodImageWallpaperEnabled
|
||||
|| info != null && info.supportsAmbientMode());
|
||||
&& (info == null || info.supportsAmbientMode());
|
||||
|
||||
mStatusBarWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
|
||||
mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
|
||||
|
||||
@@ -156,6 +156,8 @@ public class AodMaskView extends ImageView implements StatusBarStateController.S
|
||||
|
||||
private boolean checkIfNeedMask() {
|
||||
// We need mask for ImageWallpaper / LockScreen Wallpaper (Music album art).
|
||||
// Because of conflicting with another wallpaper feature,
|
||||
// we only support LockScreen wallpaper currently.
|
||||
return mWallpaperManager.getWallpaperInfo() == null || ScrimState.AOD.hasBackdrop();
|
||||
}
|
||||
|
||||
|
||||
@@ -2243,12 +2243,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
|
||||
synchronized (mLock) {
|
||||
mInAmbientMode = inAmbientMode;
|
||||
final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
|
||||
final boolean hasConnection = data != null && data.connection != null;
|
||||
final WallpaperInfo info = hasConnection ? data.connection.mInfo : null;
|
||||
|
||||
// The wallpaper info is null for image wallpaper, also use the engine in this case.
|
||||
if (hasConnection && (info == null && isAodImageWallpaperEnabled()
|
||||
|| info != null && info.supportsAmbientMode())) {
|
||||
if (data != null && data.connection != null && (data.connection.mInfo == null
|
||||
|| data.connection.mInfo.supportsAmbientMode())) {
|
||||
// TODO(multi-display) Extends this method with specific display.
|
||||
engine = data.connection.getDisplayConnectorOrCreate(DEFAULT_DISPLAY).mEngine;
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user