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