diff options
12 files changed, 914 insertions, 9 deletions
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 4a5388be2c1e..265d464d9c71 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -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" diff --git a/packages/SystemUI/res/raw/image_wallpaper_fragment_shader.glsl b/packages/SystemUI/res/raw/image_wallpaper_fragment_shader.glsl new file mode 100644 index 000000000000..586cdf3fbaae --- /dev/null +++ b/packages/SystemUI/res/raw/image_wallpaper_fragment_shader.glsl @@ -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); +}
\ No newline at end of file diff --git a/packages/SystemUI/res/raw/image_wallpaper_vertex_shader.glsl b/packages/SystemUI/res/raw/image_wallpaper_vertex_shader.glsl new file mode 100644 index 000000000000..4393e2bb0ebf --- /dev/null +++ b/packages/SystemUI/res/raw/image_wallpaper_vertex_shader.glsl @@ -0,0 +1,8 @@ +attribute vec4 aPosition; +attribute vec2 aTextureCoordinates; +varying vec2 vTextureCoordinates; + +void main() { + vTextureCoordinates = aTextureCoordinates; + gl_Position = aPosition; +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java index 2aecc24e83c0..7e645ab77a63 100644 --- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java @@ -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(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLProgram.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLProgram.java new file mode 100644 index 000000000000..d03b00bcfc85 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLProgram.java @@ -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); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLWallpaper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLWallpaper.java new file mode 100644 index 000000000000..19d85b155cba --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLWallpaper.java @@ -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); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java new file mode 100644 index 000000000000..477e7d7ebf72 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java @@ -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; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageRevealHelper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageRevealHelper.java new file mode 100644 index 000000000000..5914236ab349 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageRevealHelper.java @@ -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(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java new file mode 100644 index 000000000000..991b1161dde2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java @@ -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(); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 30d5b653deba..008eeefa8341 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -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); diff --git a/packages/SystemUI/src/com/android/systemui/wallpaper/AodMaskView.java b/packages/SystemUI/src/com/android/systemui/wallpaper/AodMaskView.java index 6ee341dd974c..f446cefb7b6f 100644 --- a/packages/SystemUI/src/com/android/systemui/wallpaper/AodMaskView.java +++ b/packages/SystemUI/src/com/android/systemui/wallpaper/AodMaskView.java @@ -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(); } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index b0ef8a0d4209..071dde74f103 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -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 { |