Home | History | Annotate | Download | only in systemui
      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
     54     public void onCreate() {
     55         super.onCreate();
     56         mWorker = new HandlerThread(TAG);
     57         mWorker.start();
     58     }
     59 
     60     @Override
     61     public Engine onCreateEngine() {
     62         return new GLEngine(this);
     63     }
     64 
     65     @Override
     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 
     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
    105         public void onCreate(SurfaceHolder surfaceHolder) {
    106             setFixedSizeAllowed(true);
    107             setOffsetNotificationsEnabled(true);
    108             updateSurfaceSize();
    109         }
    110 
    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
    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
    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 
    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
    170         public void onSurfaceCreated(SurfaceHolder holder) {
    171             mWorker.getThreadHandler().post(() -> {
    172                 mEglHelper.init(holder);
    173                 mRenderer.onSurfaceCreated();
    174             });
    175         }
    176 
    177         @Override
    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
    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
    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
    206         public void preRender() {
    207             // This method should only be invoked from worker thread.
    208             preRenderInternal();
    209         }
    210 
    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
    241         public void requestRender() {
    242             // This method should only be invoked from worker thread.
    243             requestRenderInternal();
    244         }
    245 
    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
    264         public void postRender() {
    265             // This method should only be invoked from worker thread.
    266             notifyWaitingThread();
    267             scheduleFinishRendering();
    268         }
    269 
    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 
    282         private void cancelFinishRenderingTask() {
    283             mWorker.getThreadHandler().removeCallbacks(mFinishRenderingTask);
    284         }
    285 
    286         private void scheduleFinishRendering() {
    287             cancelFinishRenderingTask();
    288             mWorker.getThreadHandler().postDelayed(mFinishRenderingTask, DELAY_FINISH_RENDERING);
    289         }
    290 
    291         private void finishRendering() {
    292             if (mEglHelper != null) {
    293                 mEglHelper.destroyEglSurface();
    294                 if (!needPreserveEglContext()) {
    295                     mEglHelper.destroyEglContext();
    296                 }
    297             }
    298         }
    299 
    300         private boolean needPreserveEglContext() {
    301             return mNeedTransition && mController != null
    302                     && mController.getState() == StatusBarState.KEYGUARD;
    303         }
    304 
    305         @Override
    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