Merge "Render ImageWallpaper with OpenGL ES and apply visual effects. (Fix bug)"

This commit is contained in:
Lucas Dupin
2019-02-16 20:28:01 +00:00
committed by Android (Google) Code Review
12 changed files with 914 additions and 9 deletions

View File

@@ -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"

View File

@@ -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);
}

View File

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

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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 {