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