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.app.ActivityManager; 20 import android.content.Context; 21 import android.graphics.Rect; 22 import android.os.HandlerThread; 23 import android.service.wallpaper.WallpaperService; 24 import android.util.Log; 25 import android.util.Size; 26 import android.view.SurfaceHolder; 27 28 import com.android.internal.annotations.VisibleForTesting; 29 import com.android.systemui.glwallpaper.EglHelper; 30 import com.android.systemui.glwallpaper.GLWallpaperRenderer; 31 import com.android.systemui.glwallpaper.ImageWallpaperRenderer; 32 import com.android.systemui.plugins.statusbar.StatusBarStateController; 33 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; 34 import com.android.systemui.statusbar.StatusBarState; 35 import com.android.systemui.statusbar.phone.DozeParameters; 36 37 import java.io.FileDescriptor; 38 import java.io.PrintWriter; 39 40 /** 41 * Default built-in wallpaper that simply shows a static image. 42 */ 43 @SuppressWarnings({"UnusedDeclaration"}) 44 public class ImageWallpaper extends WallpaperService { 45 private static final String TAG = ImageWallpaper.class.getSimpleName(); 46 // We delayed destroy render context that subsequent render requests have chance to cancel it. 47 // This is to avoid destroying then recreating render context in a very short time. 48 private static final int DELAY_FINISH_RENDERING = 1000; 49 private static final int INTERVAL_WAIT_FOR_RENDERING = 100; 50 private static final int PATIENCE_WAIT_FOR_RENDERING = 10; 51 private HandlerThread mWorker; 52 53 @Override onCreate()54 public void onCreate() { 55 super.onCreate(); 56 mWorker = new HandlerThread(TAG); 57 mWorker.start(); 58 } 59 60 @Override onCreateEngine()61 public Engine onCreateEngine() { 62 return new GLEngine(this); 63 } 64 65 @Override onDestroy()66 public void onDestroy() { 67 super.onDestroy(); 68 mWorker.quitSafely(); 69 mWorker = null; 70 } 71 72 class GLEngine extends Engine implements GLWallpaperRenderer.SurfaceProxy, StateListener { 73 // Surface is rejected if size below a threshold on some devices (ie. 8px on elfin) 74 // set min to 64 px (CTS covers this), please refer to ag/4867989 for detail. 75 @VisibleForTesting 76 static final int MIN_SURFACE_WIDTH = 64; 77 @VisibleForTesting 78 static final int MIN_SURFACE_HEIGHT = 64; 79 80 private GLWallpaperRenderer mRenderer; 81 private EglHelper mEglHelper; 82 private StatusBarStateController mController; 83 private final Runnable mFinishRenderingTask = this::finishRendering; 84 private final boolean mNeedTransition; 85 private final Object mMonitor = new Object(); 86 private boolean mNeedRedraw; 87 // This variable can only be accessed in synchronized block. 88 private boolean mWaitingForRendering; 89 GLEngine(Context context)90 GLEngine(Context context) { 91 mNeedTransition = ActivityManager.isHighEndGfx() 92 && !DozeParameters.getInstance(context).getDisplayNeedsBlanking(); 93 94 // We will preserve EGL context when we are in lock screen or aod 95 // to avoid janking in following transition, we need to release when back to home. 96 mController = Dependency.get(StatusBarStateController.class); 97 if (mController != null) { 98 mController.addCallback(this /* StateListener */); 99 } 100 mEglHelper = new EglHelper(); 101 mRenderer = new ImageWallpaperRenderer(context, this /* SurfaceProxy */); 102 } 103 104 @Override onCreate(SurfaceHolder surfaceHolder)105 public void onCreate(SurfaceHolder surfaceHolder) { 106 setFixedSizeAllowed(true); 107 setOffsetNotificationsEnabled(true); 108 updateSurfaceSize(); 109 } 110 updateSurfaceSize()111 private void updateSurfaceSize() { 112 SurfaceHolder holder = getSurfaceHolder(); 113 Size frameSize = mRenderer.reportSurfaceSize(); 114 int width = Math.max(MIN_SURFACE_WIDTH, frameSize.getWidth()); 115 int height = Math.max(MIN_SURFACE_HEIGHT, frameSize.getHeight()); 116 holder.setFixedSize(width, height); 117 } 118 119 @Override onOffsetsChanged(float xOffset, float yOffset, float xOffsetStep, float yOffsetStep, int xPixelOffset, int yPixelOffset)120 public void onOffsetsChanged(float xOffset, float yOffset, float xOffsetStep, 121 float yOffsetStep, int xPixelOffset, int yPixelOffset) { 122 mWorker.getThreadHandler().post(() -> mRenderer.updateOffsets(xOffset, yOffset)); 123 } 124 125 @Override onAmbientModeChanged(boolean inAmbientMode, long animationDuration)126 public void onAmbientModeChanged(boolean inAmbientMode, long animationDuration) { 127 if (!mNeedTransition) return; 128 mWorker.getThreadHandler().post( 129 () -> mRenderer.updateAmbientMode(inAmbientMode, animationDuration)); 130 if (inAmbientMode && animationDuration == 0) { 131 // This means that we are transiting from home to aod, to avoid 132 // race condition between window visibility and transition, 133 // we don't return until the transition is finished. See b/136643341. 134 waitForBackgroundRendering(); 135 } 136 } 137 waitForBackgroundRendering()138 private void waitForBackgroundRendering() { 139 synchronized (mMonitor) { 140 try { 141 mWaitingForRendering = true; 142 for (int patience = 1; mWaitingForRendering; patience++) { 143 mMonitor.wait(INTERVAL_WAIT_FOR_RENDERING); 144 mWaitingForRendering &= patience < PATIENCE_WAIT_FOR_RENDERING; 145 } 146 } catch (InterruptedException ex) { 147 } finally { 148 mWaitingForRendering = false; 149 } 150 } 151 } 152 153 @Override 154 public void onDestroy() { 155 if (mController != null) { 156 mController.removeCallback(this /* StateListener */); 157 } 158 mController = null; 159 160 mWorker.getThreadHandler().post(() -> { 161 mRenderer.finish(); 162 mRenderer = null; 163 mEglHelper.finish(); 164 mEglHelper = null; 165 getSurfaceHolder().getSurface().hwuiDestroy(); 166 }); 167 } 168 169 @Override onSurfaceCreated(SurfaceHolder holder)170 public void onSurfaceCreated(SurfaceHolder holder) { 171 mWorker.getThreadHandler().post(() -> { 172 mEglHelper.init(holder); 173 mRenderer.onSurfaceCreated(); 174 }); 175 } 176 177 @Override onSurfaceChanged(SurfaceHolder holder, int format, int width, int height)178 public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) { 179 mWorker.getThreadHandler().post(() -> { 180 mRenderer.onSurfaceChanged(width, height); 181 mNeedRedraw = true; 182 }); 183 } 184 185 @Override onSurfaceRedrawNeeded(SurfaceHolder holder)186 public void onSurfaceRedrawNeeded(SurfaceHolder holder) { 187 mWorker.getThreadHandler().post(() -> { 188 if (mNeedRedraw) { 189 preRender(); 190 requestRender(); 191 postRender(); 192 mNeedRedraw = false; 193 } 194 }); 195 } 196 197 @Override onStatePostChange()198 public void onStatePostChange() { 199 // When back to home, we try to release EGL, which is preserved in lock screen or aod. 200 if (mController.getState() == StatusBarState.SHADE) { 201 mWorker.getThreadHandler().post(this::scheduleFinishRendering); 202 } 203 } 204 205 @Override preRender()206 public void preRender() { 207 // This method should only be invoked from worker thread. 208 preRenderInternal(); 209 } 210 preRenderInternal()211 private void preRenderInternal() { 212 boolean contextRecreated = false; 213 Rect frame = getSurfaceHolder().getSurfaceFrame(); 214 cancelFinishRenderingTask(); 215 216 // Check if we need to recreate egl context. 217 if (!mEglHelper.hasEglContext()) { 218 mEglHelper.destroyEglSurface(); 219 if (!mEglHelper.createEglContext()) { 220 Log.w(TAG, "recreate egl context failed!"); 221 } else { 222 contextRecreated = true; 223 } 224 } 225 226 // Check if we need to recreate egl surface. 227 if (mEglHelper.hasEglContext() && !mEglHelper.hasEglSurface()) { 228 if (!mEglHelper.createEglSurface(getSurfaceHolder())) { 229 Log.w(TAG, "recreate egl surface failed!"); 230 } 231 } 232 233 // If we recreate egl context, notify renderer to setup again. 234 if (mEglHelper.hasEglContext() && mEglHelper.hasEglSurface() && contextRecreated) { 235 mRenderer.onSurfaceCreated(); 236 mRenderer.onSurfaceChanged(frame.width(), frame.height()); 237 } 238 } 239 240 @Override requestRender()241 public void requestRender() { 242 // This method should only be invoked from worker thread. 243 requestRenderInternal(); 244 } 245 requestRenderInternal()246 private void requestRenderInternal() { 247 Rect frame = getSurfaceHolder().getSurfaceFrame(); 248 boolean readyToRender = mEglHelper.hasEglContext() && mEglHelper.hasEglSurface() 249 && frame.width() > 0 && frame.height() > 0; 250 251 if (readyToRender) { 252 mRenderer.onDrawFrame(); 253 if (!mEglHelper.swapBuffer()) { 254 Log.e(TAG, "drawFrame failed!"); 255 } 256 } else { 257 Log.e(TAG, "requestRender: not ready, has context=" + mEglHelper.hasEglContext() 258 + ", has surface=" + mEglHelper.hasEglSurface() 259 + ", frame=" + frame); 260 } 261 } 262 263 @Override postRender()264 public void postRender() { 265 // This method should only be invoked from worker thread. 266 notifyWaitingThread(); 267 scheduleFinishRendering(); 268 } 269 notifyWaitingThread()270 private void notifyWaitingThread() { 271 synchronized (mMonitor) { 272 if (mWaitingForRendering) { 273 try { 274 mWaitingForRendering = false; 275 mMonitor.notify(); 276 } catch (IllegalMonitorStateException ex) { 277 } 278 } 279 } 280 } 281 cancelFinishRenderingTask()282 private void cancelFinishRenderingTask() { 283 mWorker.getThreadHandler().removeCallbacks(mFinishRenderingTask); 284 } 285 scheduleFinishRendering()286 private void scheduleFinishRendering() { 287 cancelFinishRenderingTask(); 288 mWorker.getThreadHandler().postDelayed(mFinishRenderingTask, DELAY_FINISH_RENDERING); 289 } 290 finishRendering()291 private void finishRendering() { 292 if (mEglHelper != null) { 293 mEglHelper.destroyEglSurface(); 294 if (!needPreserveEglContext()) { 295 mEglHelper.destroyEglContext(); 296 } 297 } 298 } 299 needPreserveEglContext()300 private boolean needPreserveEglContext() { 301 return mNeedTransition && mController != null 302 && mController.getState() == StatusBarState.KEYGUARD; 303 } 304 305 @Override dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args)306 protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) { 307 super.dump(prefix, fd, out, args); 308 out.print(prefix); out.print("Engine="); out.println(this); 309 310 boolean isHighEndGfx = ActivityManager.isHighEndGfx(); 311 out.print(prefix); out.print("isHighEndGfx="); out.println(isHighEndGfx); 312 313 DozeParameters dozeParameters = DozeParameters.getInstance(getApplicationContext()); 314 out.print(prefix); out.print("displayNeedsBlanking="); 315 out.println(dozeParameters != null ? dozeParameters.getDisplayNeedsBlanking() : "null"); 316 317 out.print(prefix); out.print("mNeedTransition="); out.println(mNeedTransition); 318 out.print(prefix); out.print("StatusBarState="); 319 out.println(mController != null ? mController.getState() : "null"); 320 321 out.print(prefix); out.print("valid surface="); 322 out.println(getSurfaceHolder() != null && getSurfaceHolder().getSurface() != null 323 ? getSurfaceHolder().getSurface().isValid() 324 : "null"); 325 326 out.print(prefix); out.print("surface frame="); 327 out.println(getSurfaceHolder() != null ? getSurfaceHolder().getSurfaceFrame() : "null"); 328 329 mEglHelper.dump(prefix, fd, out, args); 330 mRenderer.dump(prefix, fd, out, args); 331 } 332 } 333 } 334