1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui; 18 19 import android.graphics.Rect; 20 import android.os.Handler; 21 import android.os.HandlerThread; 22 import android.os.SystemClock; 23 import android.os.Trace; 24 import android.service.wallpaper.WallpaperService; 25 import android.util.Log; 26 import android.util.Size; 27 import android.view.SurfaceHolder; 28 29 import com.android.internal.annotations.VisibleForTesting; 30 import com.android.systemui.glwallpaper.EglHelper; 31 import com.android.systemui.glwallpaper.GLWallpaperRenderer; 32 import com.android.systemui.glwallpaper.ImageWallpaperRenderer; 33 34 import java.io.FileDescriptor; 35 import java.io.PrintWriter; 36 37 import javax.inject.Inject; 38 39 /** 40 * Default built-in wallpaper that simply shows a static image. 41 */ 42 @SuppressWarnings({"UnusedDeclaration"}) 43 public class ImageWallpaper extends WallpaperService { 44 private static final String TAG = ImageWallpaper.class.getSimpleName(); 45 // We delayed destroy render context that subsequent render requests have chance to cancel it. 46 // This is to avoid destroying then recreating render context in a very short time. 47 private static final int DELAY_FINISH_RENDERING = 1000; 48 private static final boolean DEBUG = false; 49 private HandlerThread mWorker; 50 51 @Inject ImageWallpaper()52 public ImageWallpaper() { 53 super(); 54 } 55 56 @Override onCreate()57 public void onCreate() { 58 super.onCreate(); 59 mWorker = new HandlerThread(TAG); 60 mWorker.start(); 61 } 62 63 @Override onCreateEngine()64 public Engine onCreateEngine() { 65 return new GLEngine(); 66 } 67 68 @Override onDestroy()69 public void onDestroy() { 70 super.onDestroy(); 71 mWorker.quitSafely(); 72 mWorker = null; 73 } 74 75 class GLEngine extends Engine { 76 // Surface is rejected if size below a threshold on some devices (ie. 8px on elfin) 77 // set min to 64 px (CTS covers this), please refer to ag/4867989 for detail. 78 @VisibleForTesting 79 static final int MIN_SURFACE_WIDTH = 64; 80 @VisibleForTesting 81 static final int MIN_SURFACE_HEIGHT = 64; 82 83 private GLWallpaperRenderer mRenderer; 84 private EglHelper mEglHelper; 85 private final Runnable mFinishRenderingTask = this::finishRendering; 86 private boolean mNeedRedraw; 87 GLEngine()88 GLEngine() { 89 } 90 91 @VisibleForTesting GLEngine(Handler handler)92 GLEngine(Handler handler) { 93 super(SystemClock::elapsedRealtime, handler); 94 } 95 96 @Override onCreate(SurfaceHolder surfaceHolder)97 public void onCreate(SurfaceHolder surfaceHolder) { 98 mEglHelper = getEglHelperInstance(); 99 // Deferred init renderer because we need to get wallpaper by display context. 100 mRenderer = getRendererInstance(); 101 setFixedSizeAllowed(true); 102 setOffsetNotificationsEnabled(false); 103 updateSurfaceSize(); 104 } 105 getEglHelperInstance()106 EglHelper getEglHelperInstance() { 107 return new EglHelper(); 108 } 109 getRendererInstance()110 ImageWallpaperRenderer getRendererInstance() { 111 return new ImageWallpaperRenderer(getDisplayContext()); 112 } 113 updateSurfaceSize()114 private void updateSurfaceSize() { 115 SurfaceHolder holder = getSurfaceHolder(); 116 Size frameSize = mRenderer.reportSurfaceSize(); 117 int width = Math.max(MIN_SURFACE_WIDTH, frameSize.getWidth()); 118 int height = Math.max(MIN_SURFACE_HEIGHT, frameSize.getHeight()); 119 holder.setFixedSize(width, height); 120 } 121 122 @Override shouldZoomOutWallpaper()123 public boolean shouldZoomOutWallpaper() { 124 return true; 125 } 126 127 @Override onDestroy()128 public void onDestroy() { 129 mWorker.getThreadHandler().post(() -> { 130 mRenderer.finish(); 131 mRenderer = null; 132 mEglHelper.finish(); 133 mEglHelper = null; 134 }); 135 } 136 137 @Override onSurfaceCreated(SurfaceHolder holder)138 public void onSurfaceCreated(SurfaceHolder holder) { 139 if (mWorker == null) return; 140 mWorker.getThreadHandler().post(() -> { 141 mEglHelper.init(holder, needSupportWideColorGamut()); 142 mRenderer.onSurfaceCreated(); 143 }); 144 } 145 146 @Override onSurfaceChanged(SurfaceHolder holder, int format, int width, int height)147 public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) { 148 if (mWorker == null) return; 149 mWorker.getThreadHandler().post(() -> mRenderer.onSurfaceChanged(width, height)); 150 } 151 152 @Override onSurfaceRedrawNeeded(SurfaceHolder holder)153 public void onSurfaceRedrawNeeded(SurfaceHolder holder) { 154 if (mWorker == null) return; 155 mWorker.getThreadHandler().post(this::drawFrame); 156 } 157 drawFrame()158 private void drawFrame() { 159 preRender(); 160 requestRender(); 161 postRender(); 162 } 163 preRender()164 public void preRender() { 165 // This method should only be invoked from worker thread. 166 Trace.beginSection("ImageWallpaper#preRender"); 167 preRenderInternal(); 168 Trace.endSection(); 169 } 170 preRenderInternal()171 private void preRenderInternal() { 172 boolean contextRecreated = false; 173 Rect frame = getSurfaceHolder().getSurfaceFrame(); 174 cancelFinishRenderingTask(); 175 176 // Check if we need to recreate egl context. 177 if (!mEglHelper.hasEglContext()) { 178 mEglHelper.destroyEglSurface(); 179 if (!mEglHelper.createEglContext()) { 180 Log.w(TAG, "recreate egl context failed!"); 181 } else { 182 contextRecreated = true; 183 } 184 } 185 186 // Check if we need to recreate egl surface. 187 if (mEglHelper.hasEglContext() && !mEglHelper.hasEglSurface()) { 188 if (!mEglHelper.createEglSurface(getSurfaceHolder(), needSupportWideColorGamut())) { 189 Log.w(TAG, "recreate egl surface failed!"); 190 } 191 } 192 193 // If we recreate egl context, notify renderer to setup again. 194 if (mEglHelper.hasEglContext() && mEglHelper.hasEglSurface() && contextRecreated) { 195 mRenderer.onSurfaceCreated(); 196 mRenderer.onSurfaceChanged(frame.width(), frame.height()); 197 } 198 } 199 requestRender()200 public void requestRender() { 201 // This method should only be invoked from worker thread. 202 Trace.beginSection("ImageWallpaper#requestRender"); 203 requestRenderInternal(); 204 Trace.endSection(); 205 } 206 requestRenderInternal()207 private void requestRenderInternal() { 208 Rect frame = getSurfaceHolder().getSurfaceFrame(); 209 boolean readyToRender = mEglHelper.hasEglContext() && mEglHelper.hasEglSurface() 210 && frame.width() > 0 && frame.height() > 0; 211 212 if (readyToRender) { 213 mRenderer.onDrawFrame(); 214 if (!mEglHelper.swapBuffer()) { 215 Log.e(TAG, "drawFrame failed!"); 216 } 217 } else { 218 Log.e(TAG, "requestRender: not ready, has context=" + mEglHelper.hasEglContext() 219 + ", has surface=" + mEglHelper.hasEglSurface() 220 + ", frame=" + frame); 221 } 222 } 223 postRender()224 public void postRender() { 225 // This method should only be invoked from worker thread. 226 Trace.beginSection("ImageWallpaper#postRender"); 227 scheduleFinishRendering(); 228 Trace.endSection(); 229 } 230 cancelFinishRenderingTask()231 private void cancelFinishRenderingTask() { 232 if (mWorker == null) return; 233 mWorker.getThreadHandler().removeCallbacks(mFinishRenderingTask); 234 } 235 scheduleFinishRendering()236 private void scheduleFinishRendering() { 237 if (mWorker == null) return; 238 cancelFinishRenderingTask(); 239 mWorker.getThreadHandler().postDelayed(mFinishRenderingTask, DELAY_FINISH_RENDERING); 240 } 241 finishRendering()242 private void finishRendering() { 243 Trace.beginSection("ImageWallpaper#finishRendering"); 244 if (mEglHelper != null) { 245 mEglHelper.destroyEglSurface(); 246 mEglHelper.destroyEglContext(); 247 } 248 Trace.endSection(); 249 } 250 needSupportWideColorGamut()251 private boolean needSupportWideColorGamut() { 252 return mRenderer.isWcgContent(); 253 } 254 255 @Override dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args)256 protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) { 257 super.dump(prefix, fd, out, args); 258 out.print(prefix); out.print("Engine="); out.println(this); 259 out.print(prefix); out.print("valid surface="); 260 out.println(getSurfaceHolder() != null && getSurfaceHolder().getSurface() != null 261 ? getSurfaceHolder().getSurface().isValid() 262 : "null"); 263 264 out.print(prefix); out.print("surface frame="); 265 out.println(getSurfaceHolder() != null ? getSurfaceHolder().getSurfaceFrame() : "null"); 266 267 mEglHelper.dump(prefix, fd, out, args); 268 mRenderer.dump(prefix, fd, out, args); 269 } 270 } 271 } 272