/* * 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.graphics.Rect; import android.os.Handler; import android.os.HandlerThread; import android.os.SystemClock; import android.os.Trace; 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 java.io.FileDescriptor; import java.io.PrintWriter; import javax.inject.Inject; /** * 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 boolean DEBUG = false; private HandlerThread mWorker; @Inject public ImageWallpaper() { super(); } @Override public void onCreate() { super.onCreate(); mWorker = new HandlerThread(TAG); mWorker.start(); } @Override public Engine onCreateEngine() { return new GLEngine(); } @Override public void onDestroy() { super.onDestroy(); mWorker.quitSafely(); mWorker = null; } class GLEngine extends Engine { // 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 final Runnable mFinishRenderingTask = this::finishRendering; private boolean mNeedRedraw; GLEngine() { } @VisibleForTesting GLEngine(Handler handler) { super(SystemClock::elapsedRealtime, handler); } @Override public void onCreate(SurfaceHolder surfaceHolder) { mEglHelper = getEglHelperInstance(); // Deferred init renderer because we need to get wallpaper by display context. mRenderer = getRendererInstance(); setFixedSizeAllowed(true); setOffsetNotificationsEnabled(false); updateSurfaceSize(); } EglHelper getEglHelperInstance() { return new EglHelper(); } ImageWallpaperRenderer getRendererInstance() { return new ImageWallpaperRenderer(getDisplayContext()); } 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 boolean shouldZoomOutWallpaper() { return true; } @Override public void onDestroy() { mWorker.getThreadHandler().post(() -> { mRenderer.finish(); mRenderer = null; mEglHelper.finish(); mEglHelper = null; }); } @Override public void onSurfaceCreated(SurfaceHolder holder) { if (mWorker == null) return; mWorker.getThreadHandler().post(() -> { mEglHelper.init(holder, needSupportWideColorGamut()); mRenderer.onSurfaceCreated(); }); } @Override public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) { if (mWorker == null) return; mWorker.getThreadHandler().post(() -> mRenderer.onSurfaceChanged(width, height)); } @Override public void onSurfaceRedrawNeeded(SurfaceHolder holder) { if (mWorker == null) return; mWorker.getThreadHandler().post(this::drawFrame); } private void drawFrame() { preRender(); requestRender(); postRender(); } public void preRender() { // This method should only be invoked from worker thread. Trace.beginSection("ImageWallpaper#preRender"); preRenderInternal(); Trace.endSection(); } 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(), needSupportWideColorGamut())) { 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()); } } public void requestRender() { // This method should only be invoked from worker thread. Trace.beginSection("ImageWallpaper#requestRender"); requestRenderInternal(); Trace.endSection(); } 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); } } public void postRender() { // This method should only be invoked from worker thread. Trace.beginSection("ImageWallpaper#postRender"); scheduleFinishRendering(); Trace.endSection(); } private void cancelFinishRenderingTask() { if (mWorker == null) return; mWorker.getThreadHandler().removeCallbacks(mFinishRenderingTask); } private void scheduleFinishRendering() { if (mWorker == null) return; cancelFinishRenderingTask(); mWorker.getThreadHandler().postDelayed(mFinishRenderingTask, DELAY_FINISH_RENDERING); } private void finishRendering() { Trace.beginSection("ImageWallpaper#finishRendering"); if (mEglHelper != null) { mEglHelper.destroyEglSurface(); mEglHelper.destroyEglContext(); } Trace.endSection(); } private boolean needSupportWideColorGamut() { return mRenderer.isWcgContent(); } @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); 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); } } }