Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 84ac7464 authored by Ahan Wu's avatar Ahan Wu Committed by android-build-merger
Browse files

Merge "Remove GLSurfaceView and release EGL context." into qt-dev am: 2d6f9aa6

am: df132d6a

Change-Id: I9a52e21eca975a859acb5428d7164315a5ee154c
parents bf1adc47 df132d6a
Loading
Loading
Loading
Loading
+150 −70
Original line number Diff line number Diff line
@@ -16,13 +16,22 @@

package com.android.systemui;

import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Rect;
import android.opengl.GLSurfaceView;
import android.service.wallpaper.WallpaperService;
import android.util.Log;
import android.util.Size;
import android.view.SurfaceHolder;

import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.glwallpaper.EglHelper;
import com.android.systemui.glwallpaper.GLWallpaperRenderer;
import com.android.systemui.glwallpaper.ImageWallpaperRenderer;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.DozeParameters;

/**
 * Default built-in wallpaper that simply shows a static image.
@@ -30,51 +39,98 @@ import com.android.systemui.glwallpaper.ImageWallpaperRenderer;
@SuppressWarnings({"UnusedDeclaration"})
public class ImageWallpaper extends WallpaperService {
    private static final String TAG = ImageWallpaper.class.getSimpleName();
    // We delayed destroy render context that subsequent render requests have chance to cancel it.
    // This is to avoid destroying then recreating render context in a very short time.
    private static final int DELAY_FINISH_RENDERING = 1000;

    @Override
    public Engine onCreateEngine() {
        return new GLEngine(this);
    }

    class GLEngine extends Engine {
        private GLWallpaperSurfaceView mWallpaperSurfaceView;
    class GLEngine extends Engine implements GLWallpaperRenderer.SurfaceProxy, StateListener {
        // Surface is rejected if size below a threshold on some devices (ie. 8px on elfin)
        // set min to 64 px (CTS covers this), please refer to ag/4867989 for detail.
        @VisibleForTesting
        static final int MIN_SURFACE_WIDTH = 64;
        @VisibleForTesting
        static final int MIN_SURFACE_HEIGHT = 64;

        private GLWallpaperRenderer mRenderer;
        private EglHelper mEglHelper;
        private StatusBarStateController mController;
        private final Runnable mFinishRenderingTask = this::finishRendering;
        private final boolean mNeedTransition;
        private boolean mNeedRedraw;

        GLEngine(Context context) {
            mWallpaperSurfaceView = new GLWallpaperSurfaceView(context);
            mWallpaperSurfaceView.setRenderer(
                    new ImageWallpaperRenderer(context, mWallpaperSurfaceView));
            mWallpaperSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
            setOffsetNotificationsEnabled(true);
            mNeedTransition = ActivityManager.isHighEndGfx()
                    && !DozeParameters.getInstance(context).getDisplayNeedsBlanking();

            // We will preserve EGL context when we are in lock screen or aod
            // to avoid janking in following transition, we need to release when back to home.
            mController = Dependency.get(StatusBarStateController.class);
            if (mController != null) {
                mController.addCallback(this /* StateListener */);
            }
            mEglHelper = new EglHelper();
            mRenderer = new ImageWallpaperRenderer(context, this /* SurfaceProxy */);
        }

        @Override
        public void onAmbientModeChanged(boolean inAmbientMode, long animationDuration) {
            if (mWallpaperSurfaceView != null) {
                mWallpaperSurfaceView.notifyAmbientModeChanged(inAmbientMode, animationDuration);
        public void onCreate(SurfaceHolder surfaceHolder) {
            setFixedSizeAllowed(true);
            setOffsetNotificationsEnabled(false);
            updateSurfaceSize();
        }

        private void updateSurfaceSize() {
            SurfaceHolder holder = getSurfaceHolder();
            Size frameSize = mRenderer.reportSurfaceSize();
            int width = Math.max(MIN_SURFACE_WIDTH, frameSize.getWidth());
            int height = Math.max(MIN_SURFACE_HEIGHT, frameSize.getHeight());
            holder.setFixedSize(width, height);
        }

        @Override
        public void onOffsetsChanged(float xOffset, float yOffset, float xOffsetStep,
                float yOffsetStep, int xPixelOffset, int yPixelOffset) {
            if (mWallpaperSurfaceView != null) {
                mWallpaperSurfaceView.notifyOffsetsChanged(xOffset, yOffset);
            }
        public void onAmbientModeChanged(boolean inAmbientMode, long animationDuration) {
            mRenderer.updateAmbientMode(inAmbientMode,
                    (mNeedTransition || animationDuration != 0) ? animationDuration : 0);
        }

        @Override
        public void onDestroy() {
            if (mWallpaperSurfaceView != null) {
                mWallpaperSurfaceView.onPause();
            if (mController != null) {
                mController.removeCallback(this /* StateListener */);
            }
            mController = null;
            mRenderer.finish();
            mRenderer = null;
            mEglHelper.finish();
            mEglHelper = null;
            getSurfaceHolder().getSurface().hwuiDestroy();
        }

        @Override
        public void onSurfaceCreated(SurfaceHolder holder) {
            mEglHelper.init(holder);
            mRenderer.onSurfaceCreated();
        }

        private class GLWallpaperSurfaceView extends GLSurfaceView implements ImageGLView {
            private WallpaperStatusListener mWallpaperStatusListener;
        @Override
        public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            mRenderer.onSurfaceChanged(width, height);
            mNeedRedraw = true;
        }

            GLWallpaperSurfaceView(Context context) {
                super(context);
                setEGLContextClientVersion(2);
        @Override
        public void onSurfaceRedrawNeeded(SurfaceHolder holder) {
            if (mNeedRedraw) {
                preRender();
                requestRender();
                postRender();
                mNeedRedraw = false;
            }
        }

        @Override
@@ -83,59 +139,83 @@ public class ImageWallpaper extends WallpaperService {
        }

        @Override
            public void setRenderer(Renderer renderer) {
                super.setRenderer(renderer);
                mWallpaperStatusListener = (WallpaperStatusListener) renderer;
        public void onStatePostChange() {
            // When back to home, we try to release EGL, which is preserved in lock screen or aod.
            if (mController.getState() == StatusBarState.SHADE) {
                scheduleFinishRendering();
            }
        }

        @Override
        public void preRender() {
            boolean contextRecreated = false;
            Rect frame = getSurfaceHolder().getSurfaceFrame();
            getMainThreadHandler().removeCallbacks(mFinishRenderingTask);

            // Check if we need to recreate egl context.
            if (!mEglHelper.hasEglContext()) {
                mEglHelper.destroyEglSurface();
                if (!mEglHelper.createEglContext()) {
                    Log.w(TAG, "recreate egl context failed!");
                } else {
                    contextRecreated = true;
                }
            }

            private void notifyAmbientModeChanged(boolean inAmbient, long duration) {
                if (mWallpaperStatusListener != null) {
                    mWallpaperStatusListener.onAmbientModeChanged(inAmbient, duration);
            // Check if we need to recreate egl surface.
            if (mEglHelper.hasEglContext() && !mEglHelper.hasEglSurface()) {
                if (!mEglHelper.createEglSurface(getSurfaceHolder())) {
                    Log.w(TAG, "recreate egl surface failed!");
                }
            }

            private void notifyOffsetsChanged(float xOffset, float yOffset) {
                if (mWallpaperStatusListener != null) {
                    mWallpaperStatusListener.onOffsetsChanged(
                            xOffset, yOffset, getHolder().getSurfaceFrame());
            // If we recreate egl context, notify renderer to setup again.
            if (mEglHelper.hasEglContext() && mEglHelper.hasEglSurface() && contextRecreated) {
                mRenderer.onSurfaceCreated();
                mRenderer.onSurfaceChanged(frame.width(), frame.height());
            }
        }

        @Override
            public void render() {
                requestRender();
        public void requestRender() {
            Rect frame = getSurfaceHolder().getSurfaceFrame();
            boolean readyToRender = mEglHelper.hasEglContext() && mEglHelper.hasEglSurface()
                    && frame.width() > 0 && frame.height() > 0;

            if (readyToRender) {
                mRenderer.onDrawFrame();
                if (!mEglHelper.swapBuffer()) {
                    Log.e(TAG, "drawFrame failed!");
                }
            } else {
                Log.e(TAG, "requestRender: not ready, has context=" + mEglHelper.hasEglContext()
                        + ", has surface=" + mEglHelper.hasEglSurface()
                        + ", frame=" + frame);
            }
        }

    /**
     * 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);
        @Override
        public void postRender() {
            scheduleFinishRendering();
        }

        /**
         * 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);
        private void scheduleFinishRendering() {
            getMainThreadHandler().removeCallbacks(mFinishRenderingTask);
            getMainThreadHandler().postDelayed(mFinishRenderingTask, DELAY_FINISH_RENDERING);
        }

    /**
     * An abstraction for view of GLRenderer.
     */
    public interface ImageGLView {
        private void finishRendering() {
            if (mEglHelper != null) {
                mEglHelper.destroyEglSurface();
                if (!needPreserveEglContext()) {
                    mEglHelper.destroyEglContext();
                }
            }
        }

        /**
         * Ask the view to render.
         */
        void render();
        private boolean needPreserveEglContext() {
            return mNeedTransition && mController != null
                    && mController.getState() == StatusBarState.KEYGUARD;
        }
    }
}
+230 −0
Original line number Diff line number Diff line
/*
 * 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.EGL14.EGL_ALPHA_SIZE;
import static android.opengl.EGL14.EGL_BLUE_SIZE;
import static android.opengl.EGL14.EGL_CONFIG_CAVEAT;
import static android.opengl.EGL14.EGL_CONTEXT_CLIENT_VERSION;
import static android.opengl.EGL14.EGL_DEFAULT_DISPLAY;
import static android.opengl.EGL14.EGL_DEPTH_SIZE;
import static android.opengl.EGL14.EGL_GREEN_SIZE;
import static android.opengl.EGL14.EGL_NONE;
import static android.opengl.EGL14.EGL_NO_CONTEXT;
import static android.opengl.EGL14.EGL_NO_DISPLAY;
import static android.opengl.EGL14.EGL_NO_SURFACE;
import static android.opengl.EGL14.EGL_OPENGL_ES2_BIT;
import static android.opengl.EGL14.EGL_RED_SIZE;
import static android.opengl.EGL14.EGL_RENDERABLE_TYPE;
import static android.opengl.EGL14.EGL_STENCIL_SIZE;
import static android.opengl.EGL14.EGL_SUCCESS;
import static android.opengl.EGL14.eglChooseConfig;
import static android.opengl.EGL14.eglCreateContext;
import static android.opengl.EGL14.eglCreateWindowSurface;
import static android.opengl.EGL14.eglDestroyContext;
import static android.opengl.EGL14.eglDestroySurface;
import static android.opengl.EGL14.eglGetDisplay;
import static android.opengl.EGL14.eglGetError;
import static android.opengl.EGL14.eglInitialize;
import static android.opengl.EGL14.eglMakeCurrent;
import static android.opengl.EGL14.eglSwapBuffers;
import static android.opengl.EGL14.eglTerminate;

import android.opengl.EGLConfig;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLSurface;
import android.opengl.GLUtils;
import android.util.Log;
import android.view.SurfaceHolder;

/**
 * A helper class to handle EGL management.
 */
public class EglHelper {
    private static final String TAG = EglHelper.class.getSimpleName();

    private EGLDisplay mEglDisplay;
    private EGLConfig mEglConfig;
    private EGLContext mEglContext;
    private EGLSurface mEglSurface;

    /**
     * Initialize EGL and prepare EglSurface.
     * @param surfaceHolder surface holder.
     * @return true if EglSurface is ready.
     */
    public boolean init(SurfaceHolder surfaceHolder) {
        mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
        if (mEglDisplay == EGL_NO_DISPLAY) {
            Log.w(TAG, "eglGetDisplay failed: " + GLUtils.getEGLErrorString(eglGetError()));
            return false;
        }

        if (!eglInitialize(mEglDisplay, null, 0, null, 0)) {
            Log.w(TAG, "eglInitialize failed: " + GLUtils.getEGLErrorString(eglGetError()));
            return false;
        }

        mEglConfig = chooseEglConfig();
        if (mEglConfig == null) {
            Log.w(TAG, "eglConfig not initialized!");
            return false;
        }

        if (!createEglContext()) {
            Log.w(TAG, "Can't create EGLContext!");
            return false;
        }

        if (!createEglSurface(surfaceHolder)) {
            Log.w(TAG, "Can't create EGLSurface!");
            return false;
        }

        return true;
    }

    private EGLConfig chooseEglConfig() {
        int[] configsCount = new int[1];
        EGLConfig[] configs = new EGLConfig[1];
        int[] configSpec = getConfig();
        if (!eglChooseConfig(mEglDisplay, configSpec, 0, configs, 0, 1, configsCount, 0)) {
            Log.w(TAG, "eglChooseConfig failed: " + GLUtils.getEGLErrorString(eglGetError()));
            return null;
        } else {
            if (configsCount[0] <= 0) {
                Log.w(TAG, "eglChooseConfig failed, invalid configs count: " + configsCount[0]);
                return null;
            } else {
                return configs[0];
            }
        }
    }

    private int[] getConfig() {
        return new int[] {
            EGL_RED_SIZE, 8,
            EGL_GREEN_SIZE, 8,
            EGL_BLUE_SIZE, 8,
            EGL_ALPHA_SIZE, 8,
            EGL_DEPTH_SIZE, 0,
            EGL_STENCIL_SIZE, 0,
            EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
            EGL_CONFIG_CAVEAT, EGL_NONE,
            EGL_NONE
        };
    }

    /**
     * Prepare an EglSurface.
     * @param surfaceHolder surface holder.
     * @return true if EglSurface is ready.
     */
    public boolean createEglSurface(SurfaceHolder surfaceHolder) {
        mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, null, 0);
        if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) {
            Log.w(TAG, "createWindowSurface failed: " + GLUtils.getEGLErrorString(eglGetError()));
            return false;
        }

        if (!eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
            Log.w(TAG, "eglMakeCurrent failed: " + GLUtils.getEGLErrorString(eglGetError()));
            return false;
        }

        return true;
    }

    /**
     * Destroy EglSurface.
     */
    public void destroyEglSurface() {
        if (hasEglSurface()) {
            eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
            eglDestroySurface(mEglDisplay, mEglSurface);
            mEglSurface = null;
        }
    }

    /**
     * Check if we have a valid EglSurface.
     * @return true if EglSurface is ready.
     */
    public boolean hasEglSurface() {
        return mEglSurface != null && mEglSurface != EGL_NO_SURFACE;
    }

    /**
     * Prepare EglContext.
     * @return true if EglContext is ready.
     */
    public boolean createEglContext() {
        int[] attrib_list = new int[] {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
        mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, attrib_list, 0);
        if (mEglContext == EGL_NO_CONTEXT) {
            Log.w(TAG, "eglCreateContext failed: " + GLUtils.getEGLErrorString(eglGetError()));
            return false;
        }
        return true;
    }

    /**
     * Destroy EglContext.
     */
    public void destroyEglContext() {
        if (hasEglContext()) {
            eglDestroyContext(mEglDisplay, mEglContext);
            mEglContext = null;
        }
    }

    /**
     * Check if we have EglContext.
     * @return true if EglContext is ready.
     */
    public boolean hasEglContext() {
        return mEglContext != null;
    }

    /**
     * Swap buffer to display.
     * @return true if swap successfully.
     */
    public boolean swapBuffer() {
        boolean status = eglSwapBuffers(mEglDisplay, mEglSurface);
        int error = eglGetError();
        if (error != EGL_SUCCESS) {
            Log.w(TAG, "eglSwapBuffers failed: " + GLUtils.getEGLErrorString(error));
        }
        return status;
    }

    /**
     * Destroy EglSurface and EglContext, then terminate EGL.
     */
    public void finish() {
        if (hasEglSurface()) {
            destroyEglSurface();
        }
        if (hasEglContext()) {
            destroyEglContext();
        }
        eglTerminate(mEglDisplay);
    }

}
+87 −0
Original line number Diff line number Diff line
/*
 * 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.util.Size;
import android.view.SurfaceHolder;

/**
 * A renderer which is responsible for making OpenGL calls to render a frame.
 */
public interface GLWallpaperRenderer {

    /**
     * Called when the surface is created or recreated.
     */
    void onSurfaceCreated();

    /**
     * Called when the surface changed size.
     * @param width surface width.
     * @param height surface height.
     */
    void onSurfaceChanged(int width, int height);

    /**
     * Called to draw the current frame.
     */
    void onDrawFrame();

    /**
     * Notify ambient mode is changed.
     * @param inAmbientMode true if in ambient mode.
     * @param duration duration of transition.
     */
    void updateAmbientMode(boolean inAmbientMode, long duration);

    /**
     * Ask renderer to report the surface size it needs.
     */
    Size reportSurfaceSize();

    /**
     * Called when no need to render any more.
     */
    void finish();

    /**
     * A proxy which owns surface holder.
     */
    interface SurfaceProxy {

        /**
         * Get surface holder.
         * @return surface holder.
         */
        SurfaceHolder getHolder();

        /**
         * Ask proxy to start rendering frame to surface.
         */
        void requestRender();

        /**
         * Ask proxy to prepare render context.
         */
        void preRender();

        /**
         * Ask proxy to destroy render context.
         */
        void postRender();
    }
}
+0 −72
Original line number Diff line number Diff line
@@ -195,76 +195,4 @@ class ImageGLWallpaper {
        glUniform1i(mUniTexture, 0);
    }

    /**
     * This method adjust s(x-axis), t(y-axis) texture coordinates
     * to prevent the wallpaper from being stretched.
     * The adjustment happens if either the width or height of the bitmap is larger than
     * corresponding size of the surface.
     * If both width and height are larger than corresponding size of the surface,
     * the adjustment will happen at both s, t side.
     *
     * @param bitmapWidth The width of the bitmap.
     * @param bitmapHeight The height of the bitmap.
     * @param surfaceWidth The width of the surface.
     * @param surfaceHeight The height of the surface.
     * @param xOffset The offset amount along s axis.
     * @param yOffset The offset amount along t axis.
     */
    void adjustTextureCoordinates(int bitmapWidth, int bitmapHeight,
            int surfaceWidth, int surfaceHeight, float xOffset, float yOffset) {
        float[] coordinates = TEXTURES.clone();

        if (bitmapWidth > surfaceWidth) {
            // Calculate the new s pos in pixels.
            float pixelS = (float) Math.round((bitmapWidth - surfaceWidth) * xOffset);
            // Calculate the s pos in texture coordinate.
            float coordinateS = pixelS / bitmapWidth;
            // Calculate the percentage occupied by the surface width in bitmap width.
            float surfacePercentageW = (float) surfaceWidth / bitmapWidth;
            // Need also consider the case if bitmap height is smaller than surface height.
            if (bitmapHeight < surfaceHeight) {
                // We will narrow the surface percentage to keep aspect ratio.
                surfacePercentageW *= (float) bitmapHeight / surfaceHeight;
            }
            // Determine the final s pos, also limit the legal s pos to prevent from out of range.
            float s = coordinateS + surfacePercentageW > 1f ? 1f - surfacePercentageW : coordinateS;
            // Traverse the s pos in texture coordinates array and adjust the s pos accordingly.
            for (int i = 0; i < coordinates.length; i += 2) {
                // indices 2, 4 and 6 are the end of s coordinates.
                if (i == 2 || i == 4 || i == 6) {
                    coordinates[i] = Math.min(1f, s + surfacePercentageW);
                } else {
                    coordinates[i] = s;
                }
            }
        }

        if (bitmapHeight > surfaceHeight) {
            // Calculate the new t pos in pixels.
            float pixelT = (float) Math.round((bitmapHeight - surfaceHeight) * yOffset);
            // Calculate the t pos in texture coordinate.
            float coordinateT = pixelT / bitmapHeight;
            // Calculate the percentage occupied by the surface height in bitmap height.
            float surfacePercentageH = (float) surfaceHeight / bitmapHeight;
            // Need also consider the case if bitmap width is smaller than surface width.
            if (bitmapWidth < surfaceWidth) {
                // We will narrow the surface percentage to keep aspect ratio.
                surfacePercentageH *= (float) bitmapWidth / surfaceWidth;
            }
            // Determine the final t pos, also limit the legal t pos to prevent from out of range.
            float t = coordinateT + surfacePercentageH > 1f ? 1f - surfacePercentageH : coordinateT;
            // Traverse the t pos in texture coordinates array and adjust the t pos accordingly.
            for (int i = 1; i < coordinates.length; i += 2) {
                // indices 1, 3 and 11 are the end of t coordinates.
                if (i == 1 || i == 3 || i == 11) {
                    coordinates[i] = Math.min(1f, t + surfacePercentageH);
                } else {
                    coordinates[i] = t;
                }
            }
        }

        mTextureBuffer.put(coordinates);
        mTextureBuffer.position(0);
    }
}
+19 −6

File changed.

Preview size limit exceeded, changes collapsed.

Loading