| /* |
| * Copyright (C) 2009 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; |
| |
| import android.app.ActivityManager; |
| import android.content.Context; |
| import android.graphics.Rect; |
| import android.os.HandlerThread; |
| 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; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| |
| /** |
| * Default built-in wallpaper that simply shows a static image. |
| */ |
| @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; |
| private static final int INTERVAL_WAIT_FOR_RENDERING = 100; |
| private static final int PATIENCE_WAIT_FOR_RENDERING = 10; |
| private HandlerThread mWorker; |
| |
| @Override |
| public void onCreate() { |
| super.onCreate(); |
| mWorker = new HandlerThread(TAG); |
| mWorker.start(); |
| } |
| |
| @Override |
| public Engine onCreateEngine() { |
| return new GLEngine(this); |
| } |
| |
| @Override |
| public void onDestroy() { |
| super.onDestroy(); |
| mWorker.quitSafely(); |
| mWorker = null; |
| } |
| |
| 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 final Object mMonitor = new Object(); |
| private boolean mNeedRedraw; |
| // This variable can only be accessed in synchronized block. |
| private boolean mWaitingForRendering; |
| |
| GLEngine(Context context) { |
| 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 onCreate(SurfaceHolder surfaceHolder) { |
| setFixedSizeAllowed(true); |
| setOffsetNotificationsEnabled(true); |
| 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) { |
| mWorker.getThreadHandler().post(() -> mRenderer.updateOffsets(xOffset, yOffset)); |
| } |
| |
| @Override |
| public void onAmbientModeChanged(boolean inAmbientMode, long animationDuration) { |
| if (!mNeedTransition) return; |
| mWorker.getThreadHandler().post( |
| () -> mRenderer.updateAmbientMode(inAmbientMode, animationDuration)); |
| if (inAmbientMode && animationDuration == 0) { |
| // This means that we are transiting from home to aod, to avoid |
| // race condition between window visibility and transition, |
| // we don't return until the transition is finished. See b/136643341. |
| waitForBackgroundRendering(); |
| } |
| } |
| |
| private void waitForBackgroundRendering() { |
| synchronized (mMonitor) { |
| try { |
| mWaitingForRendering = true; |
| for (int patience = 1; mWaitingForRendering; patience++) { |
| mMonitor.wait(INTERVAL_WAIT_FOR_RENDERING); |
| mWaitingForRendering &= patience < PATIENCE_WAIT_FOR_RENDERING; |
| } |
| } catch (InterruptedException ex) { |
| } finally { |
| mWaitingForRendering = false; |
| } |
| } |
| } |
| |
| @Override |
| public void onDestroy() { |
| if (mController != null) { |
| mController.removeCallback(this /* StateListener */); |
| } |
| mController = null; |
| |
| mWorker.getThreadHandler().post(() -> { |
| mRenderer.finish(); |
| mRenderer = null; |
| mEglHelper.finish(); |
| mEglHelper = null; |
| getSurfaceHolder().getSurface().hwuiDestroy(); |
| }); |
| } |
| |
| @Override |
| public void onSurfaceCreated(SurfaceHolder holder) { |
| mWorker.getThreadHandler().post(() -> { |
| mEglHelper.init(holder); |
| mRenderer.onSurfaceCreated(); |
| }); |
| } |
| |
| @Override |
| public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) { |
| mWorker.getThreadHandler().post(() -> { |
| mRenderer.onSurfaceChanged(width, height); |
| mNeedRedraw = true; |
| }); |
| } |
| |
| @Override |
| public void onSurfaceRedrawNeeded(SurfaceHolder holder) { |
| mWorker.getThreadHandler().post(() -> { |
| if (mNeedRedraw) { |
| preRender(); |
| requestRender(); |
| postRender(); |
| mNeedRedraw = false; |
| } |
| }); |
| } |
| |
| @Override |
| 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) { |
| mWorker.getThreadHandler().post(this::scheduleFinishRendering); |
| } |
| } |
| |
| @Override |
| public void preRender() { |
| // This method should only be invoked from worker thread. |
| preRenderInternal(); |
| } |
| |
| private void preRenderInternal() { |
| boolean contextRecreated = false; |
| Rect frame = getSurfaceHolder().getSurfaceFrame(); |
| cancelFinishRenderingTask(); |
| |
| // 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; |
| } |
| } |
| |
| // Check if we need to recreate egl surface. |
| if (mEglHelper.hasEglContext() && !mEglHelper.hasEglSurface()) { |
| if (!mEglHelper.createEglSurface(getSurfaceHolder())) { |
| Log.w(TAG, "recreate egl surface failed!"); |
| } |
| } |
| |
| // 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 requestRender() { |
| // This method should only be invoked from worker thread. |
| requestRenderInternal(); |
| } |
| |
| private void requestRenderInternal() { |
| 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); |
| } |
| } |
| |
| @Override |
| public void postRender() { |
| // This method should only be invoked from worker thread. |
| notifyWaitingThread(); |
| scheduleFinishRendering(); |
| } |
| |
| private void notifyWaitingThread() { |
| synchronized (mMonitor) { |
| if (mWaitingForRendering) { |
| try { |
| mWaitingForRendering = false; |
| mMonitor.notify(); |
| } catch (IllegalMonitorStateException ex) { |
| } |
| } |
| } |
| } |
| |
| private void cancelFinishRenderingTask() { |
| mWorker.getThreadHandler().removeCallbacks(mFinishRenderingTask); |
| } |
| |
| private void scheduleFinishRendering() { |
| cancelFinishRenderingTask(); |
| mWorker.getThreadHandler().postDelayed(mFinishRenderingTask, DELAY_FINISH_RENDERING); |
| } |
| |
| private void finishRendering() { |
| if (mEglHelper != null) { |
| mEglHelper.destroyEglSurface(); |
| if (!needPreserveEglContext()) { |
| mEglHelper.destroyEglContext(); |
| } |
| } |
| } |
| |
| private boolean needPreserveEglContext() { |
| return mNeedTransition && mController != null |
| && mController.getState() == StatusBarState.KEYGUARD; |
| } |
| |
| @Override |
| protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) { |
| super.dump(prefix, fd, out, args); |
| out.print(prefix); out.print("Engine="); out.println(this); |
| |
| boolean isHighEndGfx = ActivityManager.isHighEndGfx(); |
| out.print(prefix); out.print("isHighEndGfx="); out.println(isHighEndGfx); |
| |
| DozeParameters dozeParameters = DozeParameters.getInstance(getApplicationContext()); |
| out.print(prefix); out.print("displayNeedsBlanking="); |
| out.println(dozeParameters != null ? dozeParameters.getDisplayNeedsBlanking() : "null"); |
| |
| out.print(prefix); out.print("mNeedTransition="); out.println(mNeedTransition); |
| out.print(prefix); out.print("StatusBarState="); |
| out.println(mController != null ? mController.getState() : "null"); |
| |
| out.print(prefix); out.print("valid surface="); |
| out.println(getSurfaceHolder() != null && getSurfaceHolder().getSurface() != null |
| ? getSurfaceHolder().getSurface().isValid() |
| : "null"); |
| |
| out.print(prefix); out.print("surface frame="); |
| out.println(getSurfaceHolder() != null ? getSurfaceHolder().getSurfaceFrame() : "null"); |
| |
| mEglHelper.dump(prefix, fd, out, args); |
| mRenderer.dump(prefix, fd, out, args); |
| } |
| } |
| } |