ElectronBeam.java revision 98365d7663cbd82979a5700faf0050220b01084d
1/*
2 * Copyright (C) 2012 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
17package com.android.server.power;
18
19import android.graphics.Bitmap;
20import android.graphics.PixelFormat;
21import android.opengl.EGL14;
22import android.opengl.EGLConfig;
23import android.opengl.EGLContext;
24import android.opengl.EGLDisplay;
25import android.opengl.EGLSurface;
26import android.opengl.GLES10;
27import android.opengl.GLUtils;
28import android.os.Looper;
29import android.os.Process;
30import android.util.FloatMath;
31import android.util.Slog;
32import android.view.Display;
33import android.view.DisplayInfo;
34import android.view.Surface;
35import android.view.SurfaceSession;
36
37import java.io.PrintWriter;
38import java.nio.ByteBuffer;
39import java.nio.ByteOrder;
40import java.nio.FloatBuffer;
41
42/**
43 * Bzzzoooop!  *crackle*
44 *
45 * Animates a screen transition from on to off or off to on by applying
46 * some GL transformations to a screenshot.
47 *
48 * This component must only be created or accessed by the {@link Looper} thread
49 * that belongs to the {@link DisplayPowerController}.
50 */
51final class ElectronBeam {
52    private static final String TAG = "ElectronBeam";
53
54    private static final boolean DEBUG = false;
55
56    // The layer for the electron beam surface.
57    // This is currently hardcoded to be one layer above the boot animation.
58    private static final int ELECTRON_BEAM_LAYER = 0x40000001;
59
60    // The relative proportion of the animation to spend performing
61    // the horizontal stretch effect.  The remainder is spent performing
62    // the vertical stretch effect.
63    private static final float HSTRETCH_DURATION = 0.3f;
64    private static final float VSTRETCH_DURATION = 1.0f - HSTRETCH_DURATION;
65
66    // Set to true when the animation context has been fully prepared.
67    private boolean mPrepared;
68    private boolean mWarmUp;
69
70    private final Display mDisplay;
71    private final DisplayInfo mDisplayInfo = new DisplayInfo();
72    private int mDisplayLayerStack; // layer stack associated with primary display
73    private int mDisplayRotation;
74    private int mDisplayWidth;      // real width, not rotated
75    private int mDisplayHeight;     // real height, not rotated
76    private SurfaceSession mSurfaceSession;
77    private Surface mSurface;
78    private EGLDisplay mEglDisplay;
79    private EGLConfig mEglConfig;
80    private EGLContext mEglContext;
81    private EGLSurface mEglSurface;
82    private boolean mSurfaceVisible;
83
84    // Texture names.  We only use one texture, which contains the screenshot.
85    private final int[] mTexNames = new int[1];
86    private boolean mTexNamesGenerated;
87
88    // Vertex and corresponding texture coordinates.
89    // We have 4 2D vertices, so 8 elements.  The vertices form a quad.
90    private final FloatBuffer mVertexBuffer = createNativeFloatBuffer(8);
91    private final FloatBuffer mTexCoordBuffer = createNativeFloatBuffer(8);
92
93    public ElectronBeam(Display display) {
94        mDisplay = display;
95    }
96
97    /**
98     * Warms up the electron beam in preparation for turning on or off.
99     * This method prepares a GL context, and captures a screen shot.
100     *
101     * @param warmUp True if the electron beam is about to be turned on, false if
102     * it is about to be turned off.
103     * @return True if the electron beam is ready, false if it is uncontrollable.
104     */
105    public boolean prepare(boolean warmUp) {
106        if (DEBUG) {
107            Slog.d(TAG, "prepare: warmUp=" + warmUp);
108        }
109
110        mWarmUp = warmUp;
111
112        // Get the display size and adjust it for rotation.
113        mDisplay.getDisplayInfo(mDisplayInfo);
114        mDisplayLayerStack = mDisplay.getLayerStack();
115        mDisplayRotation = mDisplayInfo.rotation;
116        if (mDisplayRotation == Surface.ROTATION_90
117                || mDisplayRotation == Surface.ROTATION_270) {
118            mDisplayWidth = mDisplayInfo.logicalHeight;
119            mDisplayHeight = mDisplayInfo.logicalWidth;
120        } else {
121            mDisplayWidth = mDisplayInfo.logicalWidth;
122            mDisplayHeight = mDisplayInfo.logicalHeight;
123        }
124
125        // Prepare the surface for drawing.
126        if (!createEglContext()
127                || !createEglSurface()
128                || !captureScreenshotTextureAndSetViewport()) {
129            dismiss();
130            return false;
131        }
132
133        mPrepared = true;
134        return true;
135    }
136
137    /**
138     * Dismisses the electron beam animation surface and cleans up.
139     *
140     * To prevent stray photons from leaking out after the electron beam has been
141     * turned off, it is a good idea to defer dismissing the animation until the
142     * electron beam has been turned back on fully.
143     */
144    public void dismiss() {
145        if (DEBUG) {
146            Slog.d(TAG, "dismiss");
147        }
148
149        destroyScreenshotTexture();
150        destroyEglSurface();
151        mPrepared = false;
152    }
153
154    /**
155     * Draws an animation frame showing the electron beam activated at the
156     * specified level.
157     *
158     * @param level The electron beam level.
159     * @return True if successful.
160     */
161    public boolean draw(float level) {
162        if (DEBUG) {
163            Slog.d(TAG, "drawFrame: level=" + level);
164        }
165
166        if (!attachEglContext()) {
167            return false;
168        }
169        try {
170            // Clear frame to solid black.
171            GLES10.glClearColor(0f, 0f, 0f, 1f);
172            GLES10.glClear(GLES10.GL_COLOR_BUFFER_BIT);
173
174            // Draw the frame.
175            if (level < HSTRETCH_DURATION) {
176                drawHStretch(1.0f - (level / HSTRETCH_DURATION));
177            } else {
178                drawVStretch(1.0f - ((level - HSTRETCH_DURATION) / VSTRETCH_DURATION));
179            }
180            if (checkGlErrors("drawFrame")) {
181                return false;
182            }
183
184            EGL14.eglSwapBuffers(mEglDisplay, mEglSurface);
185        } finally {
186            detachEglContext();
187        }
188
189        return showEglSurface();
190    }
191
192    /**
193     * Draws a frame where the content of the electron beam is collapsing inwards upon
194     * itself vertically with red / green / blue channels dispersing and eventually
195     * merging down to a single horizontal line.
196     *
197     * @param stretch The stretch factor.  0.0 is no collapse, 1.0 is full collapse.
198     */
199    private void drawVStretch(float stretch) {
200        // compute interpolation scale factors for each color channel
201        final float ar = scurve(stretch, 7.5f);
202        final float ag = scurve(stretch, 8.0f);
203        final float ab = scurve(stretch, 8.5f);
204        if (DEBUG) {
205            Slog.d(TAG, "drawVStretch: stretch=" + stretch
206                    + ", ar=" + ar + ", ag=" + ag + ", ab=" + ab);
207        }
208
209        // set blending
210        GLES10.glBlendFunc(GLES10.GL_ONE, GLES10.GL_ONE);
211        GLES10.glEnable(GLES10.GL_BLEND);
212
213        // bind vertex buffer
214        GLES10.glVertexPointer(2, GLES10.GL_FLOAT, 0, mVertexBuffer);
215        GLES10.glEnableClientState(GLES10.GL_VERTEX_ARRAY);
216
217        // bind texture and set blending for drawing planes
218        GLES10.glBindTexture(GLES10.GL_TEXTURE_2D, mTexNames[0]);
219        GLES10.glTexEnvx(GLES10.GL_TEXTURE_ENV, GLES10.GL_TEXTURE_ENV_MODE,
220                mWarmUp ? GLES10.GL_MODULATE : GLES10.GL_REPLACE);
221        GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D,
222                GLES10.GL_TEXTURE_MAG_FILTER, GLES10.GL_LINEAR);
223        GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D,
224                GLES10.GL_TEXTURE_MIN_FILTER, GLES10.GL_LINEAR);
225        GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D,
226                GLES10.GL_TEXTURE_WRAP_S, GLES10.GL_CLAMP_TO_EDGE);
227        GLES10.glTexParameterx(GLES10.GL_TEXTURE_2D,
228                GLES10.GL_TEXTURE_WRAP_T, GLES10.GL_CLAMP_TO_EDGE);
229        GLES10.glEnable(GLES10.GL_TEXTURE_2D);
230        GLES10.glTexCoordPointer(2, GLES10.GL_FLOAT, 0, mTexCoordBuffer);
231        GLES10.glEnableClientState(GLES10.GL_TEXTURE_COORD_ARRAY);
232
233        // draw the red plane
234        setVStretchQuad(mVertexBuffer, mDisplayWidth, mDisplayHeight, ar);
235        GLES10.glColorMask(true, false, false, true);
236        GLES10.glDrawArrays(GLES10.GL_TRIANGLE_FAN, 0, 4);
237
238        // draw the green plane
239        setVStretchQuad(mVertexBuffer, mDisplayWidth, mDisplayHeight, ag);
240        GLES10.glColorMask(false, true, false, true);
241        GLES10.glDrawArrays(GLES10.GL_TRIANGLE_FAN, 0, 4);
242
243        // draw the blue plane
244        setVStretchQuad(mVertexBuffer, mDisplayWidth, mDisplayHeight, ab);
245        GLES10.glColorMask(false, false, true, true);
246        GLES10.glDrawArrays(GLES10.GL_TRIANGLE_FAN, 0, 4);
247
248        // clean up after drawing planes
249        GLES10.glDisable(GLES10.GL_TEXTURE_2D);
250        GLES10.glDisableClientState(GLES10.GL_TEXTURE_COORD_ARRAY);
251        GLES10.glColorMask(true, true, true, true);
252
253        // draw the white highlight (we use the last vertices)
254        if (!mWarmUp) {
255            GLES10.glColor4f(ag, ag, ag, 1.0f);
256            GLES10.glDrawArrays(GLES10.GL_TRIANGLE_FAN, 0, 4);
257        }
258
259        // clean up
260        GLES10.glDisableClientState(GLES10.GL_VERTEX_ARRAY);
261        GLES10.glDisable(GLES10.GL_BLEND);
262    }
263
264    /**
265     * Draws a frame where the electron beam has been stretched out into
266     * a thin white horizontal line that fades as it expands outwards.
267     *
268     * @param stretch The stretch factor.  0.0 is no stretch / no fade,
269     * 1.0 is maximum stretch / maximum fade.
270     */
271    private void drawHStretch(float stretch) {
272        // compute interpolation scale factor
273        final float ag = scurve(stretch, 8.0f);
274        if (DEBUG) {
275            Slog.d(TAG, "drawHStretch: stretch=" + stretch + ", ag=" + ag);
276        }
277
278        if (stretch < 1.0f) {
279            // bind vertex buffer
280            GLES10.glVertexPointer(2, GLES10.GL_FLOAT, 0, mVertexBuffer);
281            GLES10.glEnableClientState(GLES10.GL_VERTEX_ARRAY);
282
283            // draw narrow fading white line
284            setHStretchQuad(mVertexBuffer, mDisplayWidth, mDisplayHeight, ag);
285            GLES10.glColor4f(1.0f - ag, 1.0f - ag, 1.0f - ag, 1.0f);
286            GLES10.glDrawArrays(GLES10.GL_TRIANGLE_FAN, 0, 4);
287
288            // clean up
289            GLES10.glDisableClientState(GLES10.GL_VERTEX_ARRAY);
290        }
291    }
292
293    private static void setVStretchQuad(FloatBuffer vtx, float dw, float dh, float a) {
294        final float w = dw + (dw * a);
295        final float h = dh - (dh * a);
296        final float x = (dw - w) * 0.5f;
297        final float y = (dh - h) * 0.5f;
298        setQuad(vtx, x, y, w, h);
299    }
300
301    private static void setHStretchQuad(FloatBuffer vtx, float dw, float dh, float a) {
302        final float w = dw + (dw * a);
303        final float h = 1.0f;
304        final float x = (dw - w) * 0.5f;
305        final float y = (dh - h) * 0.5f;
306        setQuad(vtx, x, y, w, h);
307    }
308
309    private static void setQuad(FloatBuffer vtx, float x, float y, float w, float h) {
310        if (DEBUG) {
311            Slog.d(TAG, "setQuad: x=" + x + ", y=" + y + ", w=" + w + ", h=" + h);
312        }
313        vtx.put(0, x);
314        vtx.put(1, y);
315        vtx.put(2, x);
316        vtx.put(3, y + h);
317        vtx.put(4, x + w);
318        vtx.put(5, y + h);
319        vtx.put(6, x + w);
320        vtx.put(7, y);
321    }
322
323    private boolean captureScreenshotTextureAndSetViewport() {
324        // TODO: Use a SurfaceTexture to avoid the extra texture upload.
325        Bitmap bitmap = Surface.screenshot(mDisplayWidth, mDisplayHeight,
326                0, ELECTRON_BEAM_LAYER - 1);
327        if (bitmap == null) {
328            Slog.e(TAG, "Could not take a screenshot!");
329            return false;
330        }
331        try {
332            if (!attachEglContext()) {
333                return false;
334            }
335            try {
336                if (!mTexNamesGenerated) {
337                    GLES10.glGenTextures(1, mTexNames, 0);
338                    if (checkGlErrors("glGenTextures")) {
339                        return false;
340                    }
341                    mTexNamesGenerated = true;
342                }
343
344                GLES10.glBindTexture(GLES10.GL_TEXTURE_2D, mTexNames[0]);
345                if (checkGlErrors("glBindTexture")) {
346                    return false;
347                }
348
349                float u = 1.0f;
350                float v = 1.0f;
351                GLUtils.texImage2D(GLES10.GL_TEXTURE_2D, 0, bitmap, 0);
352                if (checkGlErrors("glTexImage2D, first try", false)) {
353                    // Try a power of two size texture instead.
354                    int tw = nextPowerOfTwo(mDisplayWidth);
355                    int th = nextPowerOfTwo(mDisplayHeight);
356                    int format = GLUtils.getInternalFormat(bitmap);
357                    GLES10.glTexImage2D(GLES10.GL_TEXTURE_2D, 0,
358                            format, tw, th, 0,
359                            format, GLES10.GL_UNSIGNED_BYTE, null);
360                    if (checkGlErrors("glTexImage2D, second try")) {
361                        return false;
362                    }
363
364                    GLUtils.texSubImage2D(GLES10.GL_TEXTURE_2D, 0, 0, 0, bitmap);
365                    if (checkGlErrors("glTexSubImage2D")) {
366                        return false;
367                    }
368
369                    u = (float)mDisplayWidth / tw;
370                    v = (float)mDisplayHeight / th;
371                }
372
373                // Set up texture coordinates for a quad.
374                // We might need to change this if the texture ends up being
375                // a different size from the display for some reason.
376                mTexCoordBuffer.put(0, 0f);
377                mTexCoordBuffer.put(1, v);
378                mTexCoordBuffer.put(2, 0f);
379                mTexCoordBuffer.put(3, 0f);
380                mTexCoordBuffer.put(4, u);
381                mTexCoordBuffer.put(5, 0f);
382                mTexCoordBuffer.put(6, u);
383                mTexCoordBuffer.put(7, v);
384
385                // Set up our viewport.
386                GLES10.glViewport(0, 0, mDisplayWidth, mDisplayHeight);
387                GLES10.glMatrixMode(GLES10.GL_PROJECTION);
388                GLES10.glLoadIdentity();
389                GLES10.glOrthof(0, mDisplayWidth, 0, mDisplayHeight, 0, 1);
390                GLES10.glMatrixMode(GLES10.GL_MODELVIEW);
391                GLES10.glLoadIdentity();
392                GLES10.glMatrixMode(GLES10.GL_TEXTURE);
393                GLES10.glLoadIdentity();
394            } finally {
395                detachEglContext();
396            }
397        } finally {
398            bitmap.recycle();
399        }
400        return true;
401    }
402
403    private void destroyScreenshotTexture() {
404        if (mTexNamesGenerated) {
405            mTexNamesGenerated = false;
406            if (attachEglContext()) {
407                try {
408                    GLES10.glDeleteTextures(1, mTexNames, 0);
409                    checkGlErrors("glDeleteTextures");
410                } finally {
411                    detachEglContext();
412                }
413            }
414        }
415    }
416
417    private boolean createEglContext() {
418        if (mEglDisplay == null) {
419            mEglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
420            if (mEglDisplay == EGL14.EGL_NO_DISPLAY) {
421                logEglError("eglGetDisplay");
422                return false;
423            }
424
425            int[] version = new int[2];
426            if (!EGL14.eglInitialize(mEglDisplay, version, 0, version, 1)) {
427                mEglDisplay = null;
428                logEglError("eglInitialize");
429                return false;
430            }
431        }
432
433        if (mEglConfig == null) {
434            int[] eglConfigAttribList = new int[] {
435                    EGL14.EGL_RED_SIZE, 8,
436                    EGL14.EGL_GREEN_SIZE, 8,
437                    EGL14.EGL_BLUE_SIZE, 8,
438                    EGL14.EGL_ALPHA_SIZE, 8,
439                    EGL14.EGL_NONE
440            };
441            int[] numEglConfigs = new int[1];
442            EGLConfig[] eglConfigs = new EGLConfig[1];
443            if (!EGL14.eglChooseConfig(mEglDisplay, eglConfigAttribList, 0,
444                    eglConfigs, 0, eglConfigs.length, numEglConfigs, 0)) {
445                logEglError("eglChooseConfig");
446                return false;
447            }
448            mEglConfig = eglConfigs[0];
449        }
450
451        if (mEglContext == null) {
452            int[] eglContextAttribList = new int[] {
453                    EGL14.EGL_NONE
454            };
455            mEglContext = EGL14.eglCreateContext(mEglDisplay, mEglConfig,
456                    EGL14.EGL_NO_CONTEXT, eglContextAttribList, 0);
457            if (mEglContext == null) {
458                logEglError("eglCreateContext");
459                return false;
460            }
461        }
462        return true;
463    }
464
465    /* not used because it is too expensive to create / destroy contexts all of the time
466    private void destroyEglContext() {
467        if (mEglContext != null) {
468            if (!EGL14.eglDestroyContext(mEglDisplay, mEglContext)) {
469                logEglError("eglDestroyContext");
470            }
471            mEglContext = null;
472        }
473    }*/
474
475    private boolean createEglSurface() {
476        if (mSurfaceSession == null) {
477            mSurfaceSession = new SurfaceSession();
478        }
479
480        Surface.openTransaction();
481        try {
482            if (mSurface == null) {
483                try {
484                    mSurface = new Surface(mSurfaceSession, Process.myPid(),
485                            "ElectronBeam", mDisplayLayerStack, mDisplayWidth, mDisplayHeight,
486                            PixelFormat.OPAQUE, Surface.OPAQUE | Surface.HIDDEN);
487                } catch (Surface.OutOfResourcesException ex) {
488                    Slog.e(TAG, "Unable to create surface.", ex);
489                    return false;
490                }
491            }
492
493            mSurface.setSize(mDisplayWidth, mDisplayHeight);
494
495            switch (mDisplayRotation) {
496                case Surface.ROTATION_0:
497                    mSurface.setPosition(0, 0);
498                    mSurface.setMatrix(1, 0, 0, 1);
499                    break;
500                case Surface.ROTATION_90:
501                    mSurface.setPosition(0, mDisplayWidth);
502                    mSurface.setMatrix(0, -1, 1, 0);
503                    break;
504                case Surface.ROTATION_180:
505                    mSurface.setPosition(mDisplayWidth, mDisplayHeight);
506                    mSurface.setMatrix(-1, 0, 0, -1);
507                    break;
508                case Surface.ROTATION_270:
509                    mSurface.setPosition(mDisplayHeight, 0);
510                    mSurface.setMatrix(0, 1, -1, 0);
511                    break;
512            }
513        } finally {
514            Surface.closeTransaction();
515        }
516
517        if (mEglSurface == null) {
518            int[] eglSurfaceAttribList = new int[] {
519                    EGL14.EGL_NONE
520            };
521            mEglSurface = EGL14.eglCreateWindowSurface(mEglDisplay, mEglConfig, mSurface,
522                    eglSurfaceAttribList, 0);
523            if (mEglSurface == null) {
524                logEglError("eglCreateWindowSurface");
525                return false;
526            }
527        }
528        return true;
529    }
530
531    private void destroyEglSurface() {
532        if (mEglSurface != null) {
533            if (!EGL14.eglDestroySurface(mEglDisplay, mEglSurface)) {
534                logEglError("eglDestroySurface");
535            }
536            mEglSurface = null;
537        }
538
539        if (mSurface != null) {
540            Surface.openTransaction();
541            try {
542                mSurface.destroy();
543            } finally {
544                Surface.closeTransaction();
545            }
546            mSurface = null;
547            mSurfaceVisible = false;
548        }
549    }
550
551    private boolean showEglSurface() {
552        if (!mSurfaceVisible) {
553            Surface.openTransaction();
554            try {
555                mSurface.setLayer(ELECTRON_BEAM_LAYER);
556                mSurface.show();
557            } finally {
558                Surface.closeTransaction();
559            }
560            mSurfaceVisible = true;
561        }
562        return true;
563    }
564
565    private boolean attachEglContext() {
566        if (mEglSurface == null) {
567            return false;
568        }
569        if (!EGL14.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
570            logEglError("eglMakeCurrent");
571            return false;
572        }
573        return true;
574    }
575
576    private void detachEglContext() {
577        if (mEglDisplay != null) {
578            EGL14.eglMakeCurrent(mEglDisplay,
579                    EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
580        }
581    }
582
583    /**
584     * Interpolates a value in the range 0 .. 1 along a sigmoid curve
585     * yielding a result in the range 0 .. 1 scaled such that:
586     * scurve(0) == 0, scurve(0.5) == 0.5, scurve(1) == 1.
587     */
588    private static float scurve(float value, float s) {
589        // A basic sigmoid has the form y = 1.0f / FloatMap.exp(-x * s).
590        // Here we take the input datum and shift it by 0.5 so that the
591        // domain spans the range -0.5 .. 0.5 instead of 0 .. 1.
592        final float x = value - 0.5f;
593
594        // Next apply the sigmoid function to the scaled value
595        // which produces a value in the range 0 .. 1 so we subtract
596        // 0.5 to get a value in the range -0.5 .. 0.5 instead.
597        final float y = sigmoid(x, s) - 0.5f;
598
599        // To obtain the desired boundary conditions we need to scale
600        // the result so that it fills a range of -1 .. 1.
601        final float v = sigmoid(0.5f, s) - 0.5f;
602
603        // And finally remap the value back to a range of 0 .. 1.
604        return y / v * 0.5f + 0.5f;
605    }
606
607    private static float sigmoid(float x, float s) {
608        return 1.0f / (1.0f + FloatMath.exp(-x * s));
609    }
610
611    private static int nextPowerOfTwo(int value) {
612        return 1 << (32 - Integer.numberOfLeadingZeros(value));
613    }
614
615    private static FloatBuffer createNativeFloatBuffer(int size) {
616        ByteBuffer bb = ByteBuffer.allocateDirect(size * 4);
617        bb.order(ByteOrder.nativeOrder());
618        return bb.asFloatBuffer();
619    }
620
621    private static void logEglError(String func) {
622        Slog.e(TAG, func + " failed: error " + EGL14.eglGetError(), new Throwable());
623    }
624
625    private static boolean checkGlErrors(String func) {
626        return checkGlErrors(func, true);
627    }
628
629    private static boolean checkGlErrors(String func, boolean log) {
630        boolean hadError = false;
631        int error;
632        while ((error = GLES10.glGetError()) != GLES10.GL_NO_ERROR) {
633            if (log) {
634                Slog.e(TAG, func + " failed: error " + error, new Throwable());
635            }
636            hadError = true;
637        }
638        return hadError;
639    }
640
641    public void dump(PrintWriter pw) {
642        pw.println();
643        pw.println("Electron Beam State:");
644        pw.println("  mPrepared=" + mPrepared);
645        pw.println("  mWarmUp=" + mWarmUp);
646        pw.println("  mDisplayLayerStack=" + mDisplayLayerStack);
647        pw.println("  mDisplayRotation=" + mDisplayRotation);
648        pw.println("  mDisplayWidth=" + mDisplayWidth);
649        pw.println("  mDisplayHeight=" + mDisplayHeight);
650        pw.println("  mSurfaceVisible=" + mSurfaceVisible);
651    }
652}
653