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