ImageWallpaper.java revision 98365d7663cbd82979a5700faf0050220b01084d
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 17package com.android.systemui; 18 19import android.app.ActivityManager; 20import android.app.WallpaperManager; 21import android.content.BroadcastReceiver; 22import android.content.Context; 23import android.content.Intent; 24import android.graphics.Bitmap; 25import android.graphics.Canvas; 26import android.graphics.Rect; 27import android.graphics.Region.Op; 28import android.opengl.GLUtils; 29import android.os.SystemProperties; 30import android.renderscript.Matrix4f; 31import android.service.wallpaper.WallpaperService; 32import android.util.Log; 33import android.view.Display; 34import android.view.MotionEvent; 35import android.view.SurfaceHolder; 36import android.view.WindowManager; 37 38import javax.microedition.khronos.egl.EGL10; 39import javax.microedition.khronos.egl.EGLConfig; 40import javax.microedition.khronos.egl.EGLContext; 41import javax.microedition.khronos.egl.EGLDisplay; 42import javax.microedition.khronos.egl.EGLSurface; 43import javax.microedition.khronos.opengles.GL; 44import java.io.IOException; 45import java.nio.ByteBuffer; 46import java.nio.ByteOrder; 47import java.nio.FloatBuffer; 48 49import static android.opengl.GLES20.*; 50import static javax.microedition.khronos.egl.EGL10.*; 51 52/** 53 * Default built-in wallpaper that simply shows a static image. 54 */ 55@SuppressWarnings({"UnusedDeclaration"}) 56public class ImageWallpaper extends WallpaperService { 57 private static final String TAG = "ImageWallpaper"; 58 private static final String GL_LOG_TAG = "ImageWallpaperGL"; 59 private static final boolean DEBUG = false; 60 private static final String PROPERTY_KERNEL_QEMU = "ro.kernel.qemu"; 61 62 static final boolean FIXED_SIZED_SURFACE = true; 63 static final boolean USE_OPENGL = true; 64 65 WallpaperManager mWallpaperManager; 66 67 boolean mIsHwAccelerated; 68 69 @Override 70 public void onCreate() { 71 super.onCreate(); 72 mWallpaperManager = (WallpaperManager) getSystemService(WALLPAPER_SERVICE); 73 74 //noinspection PointlessBooleanExpression,ConstantConditions 75 if (FIXED_SIZED_SURFACE && USE_OPENGL) { 76 if (!isEmulator()) { 77 mIsHwAccelerated = ActivityManager.isHighEndGfx(); 78 } 79 } 80 } 81 82 private static boolean isEmulator() { 83 return "1".equals(SystemProperties.get(PROPERTY_KERNEL_QEMU, "0")); 84 } 85 86 public Engine onCreateEngine() { 87 return new DrawableEngine(); 88 } 89 90 class DrawableEngine extends Engine { 91 static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; 92 static final int EGL_OPENGL_ES2_BIT = 4; 93 94 private final Object mLock = new Object[0]; 95 96 // TODO: Not currently used, keeping around until we know we don't need it 97 @SuppressWarnings({"UnusedDeclaration"}) 98 private WallpaperObserver mReceiver; 99 100 Bitmap mBackground; 101 int mBackgroundWidth = -1, mBackgroundHeight = -1; 102 float mXOffset; 103 float mYOffset; 104 105 boolean mVisible = true; 106 boolean mRedrawNeeded; 107 boolean mOffsetsChanged; 108 int mLastXTranslation; 109 int mLastYTranslation; 110 111 private EGL10 mEgl; 112 private EGLDisplay mEglDisplay; 113 private EGLConfig mEglConfig; 114 private EGLContext mEglContext; 115 private EGLSurface mEglSurface; 116 private GL mGL; 117 118 private static final String sSimpleVS = 119 "attribute vec4 position;\n" + 120 "attribute vec2 texCoords;\n" + 121 "varying vec2 outTexCoords;\n" + 122 "uniform mat4 projection;\n" + 123 "\nvoid main(void) {\n" + 124 " outTexCoords = texCoords;\n" + 125 " gl_Position = projection * position;\n" + 126 "}\n\n"; 127 private static final String sSimpleFS = 128 "precision mediump float;\n\n" + 129 "varying vec2 outTexCoords;\n" + 130 "uniform sampler2D texture;\n" + 131 "\nvoid main(void) {\n" + 132 " gl_FragColor = texture2D(texture, outTexCoords);\n" + 133 "}\n\n"; 134 135 private static final int FLOAT_SIZE_BYTES = 4; 136 private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES; 137 private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0; 138 private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3; 139 140 class WallpaperObserver extends BroadcastReceiver { 141 public void onReceive(Context context, Intent intent) { 142 if (DEBUG) { 143 Log.d(TAG, "onReceive"); 144 } 145 146 synchronized (mLock) { 147 mBackgroundWidth = mBackgroundHeight = -1; 148 mBackground = null; 149 mRedrawNeeded = true; 150 drawFrameLocked(); 151 } 152 } 153 } 154 155 public DrawableEngine() { 156 super(); 157 setFixedSizeAllowed(true); 158 } 159 160 @Override 161 public void onCreate(SurfaceHolder surfaceHolder) { 162 if (DEBUG) { 163 Log.d(TAG, "onCreate"); 164 } 165 166 super.onCreate(surfaceHolder); 167 168 // TODO: Don't need this currently because the wallpaper service 169 // will restart the image wallpaper whenever the image changes. 170 //IntentFilter filter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED); 171 //mReceiver = new WallpaperObserver(); 172 //registerReceiver(mReceiver, filter, null, mHandler); 173 174 updateSurfaceSize(surfaceHolder); 175 176 setOffsetNotificationsEnabled(false); 177 } 178 179 @Override 180 public void onDestroy() { 181 super.onDestroy(); 182 if (mReceiver != null) { 183 unregisterReceiver(mReceiver); 184 } 185 } 186 187 @Override 188 public void onDesiredSizeChanged(int desiredWidth, int desiredHeight) { 189 super.onDesiredSizeChanged(desiredWidth, desiredHeight); 190 SurfaceHolder surfaceHolder = getSurfaceHolder(); 191 if (surfaceHolder != null) { 192 updateSurfaceSize(surfaceHolder); 193 } 194 } 195 196 void updateSurfaceSize(SurfaceHolder surfaceHolder) { 197 if (FIXED_SIZED_SURFACE) { 198 // Used a fixed size surface, because we are special. We can do 199 // this because we know the current design of window animations doesn't 200 // cause this to break. 201 surfaceHolder.setFixedSize(getDesiredMinimumWidth(), getDesiredMinimumHeight()); 202 } else { 203 surfaceHolder.setSizeFromLayout(); 204 } 205 } 206 207 @Override 208 public void onVisibilityChanged(boolean visible) { 209 if (DEBUG) { 210 Log.d(TAG, "onVisibilityChanged: visible=" + visible); 211 } 212 213 synchronized (mLock) { 214 if (mVisible != visible) { 215 if (DEBUG) { 216 Log.d(TAG, "Visibility changed to visible=" + visible); 217 } 218 mVisible = visible; 219 drawFrameLocked(); 220 } 221 } 222 } 223 224 @Override 225 public void onTouchEvent(MotionEvent event) { 226 super.onTouchEvent(event); 227 } 228 229 @Override 230 public void onOffsetsChanged(float xOffset, float yOffset, 231 float xOffsetStep, float yOffsetStep, 232 int xPixels, int yPixels) { 233 if (DEBUG) { 234 Log.d(TAG, "onOffsetsChanged: xOffset=" + xOffset + ", yOffset=" + yOffset 235 + ", xOffsetStep=" + xOffsetStep + ", yOffsetStep=" + yOffsetStep 236 + ", xPixels=" + xPixels + ", yPixels=" + yPixels); 237 } 238 239 synchronized (mLock) { 240 if (mXOffset != xOffset || mYOffset != yOffset) { 241 if (DEBUG) { 242 Log.d(TAG, "Offsets changed to (" + xOffset + "," + yOffset + ")."); 243 } 244 mXOffset = xOffset; 245 mYOffset = yOffset; 246 mOffsetsChanged = true; 247 } 248 drawFrameLocked(); 249 } 250 } 251 252 @Override 253 public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) { 254 if (DEBUG) { 255 Log.d(TAG, "onSurfaceChanged: width=" + width + ", height=" + height); 256 } 257 258 super.onSurfaceChanged(holder, format, width, height); 259 260 synchronized (mLock) { 261 mRedrawNeeded = true; 262 drawFrameLocked(); 263 } 264 } 265 266 void drawFrameLocked() { 267 if (!mVisible) { 268 if (DEBUG) { 269 Log.d(TAG, "Suppressed drawFrame since wallpaper is not visible."); 270 } 271 return; 272 } 273 if (!mRedrawNeeded && !mOffsetsChanged) { 274 if (DEBUG) { 275 Log.d(TAG, "Suppressed drawFrame since redraw is not needed " 276 + "and offsets have not changed."); 277 } 278 return; 279 } 280 281 // If we don't yet know the size of the wallpaper bitmap, 282 // we need to get it now. 283 boolean updateWallpaper = mBackgroundWidth < 0 || mBackgroundHeight < 0 ; 284 285 // If we somehow got to this point after we have last flushed 286 // the wallpaper, well we really need it to draw again. So 287 // seems like we need to reload it. Ouch. 288 updateWallpaper = updateWallpaper || mBackground == null; 289 290 if (updateWallpaper) { 291 updateWallpaperLocked(); 292 } 293 294 SurfaceHolder sh = getSurfaceHolder(); 295 final Rect frame = sh.getSurfaceFrame(); 296 final int dw = frame.width(); 297 final int dh = frame.height(); 298 final int availw = dw - mBackgroundWidth; 299 final int availh = dh - mBackgroundHeight; 300 int xPixels = availw < 0 ? (int)(availw * mXOffset + .5f) : (availw / 2); 301 int yPixels = availh < 0 ? (int)(availh * mYOffset + .5f) : (availh / 2); 302 303 mOffsetsChanged = false; 304 if (!mRedrawNeeded && xPixels == mLastXTranslation && yPixels == mLastYTranslation) { 305 if (DEBUG) { 306 Log.d(TAG, "Suppressed drawFrame since the image has not " 307 + "actually moved an integral number of pixels."); 308 } 309 return; 310 } 311 mRedrawNeeded = false; 312 mLastXTranslation = xPixels; 313 mLastYTranslation = yPixels; 314 315 if (mIsHwAccelerated) { 316 if (!drawWallpaperWithOpenGL(sh, availw, availh, xPixels, yPixels)) { 317 drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels); 318 } 319 } else { 320 drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels); 321 } 322 323 if (FIXED_SIZED_SURFACE) { 324 // If the surface is fixed-size, we should only need to 325 // draw it once and then we'll let the window manager 326 // position it appropriately. As such, we no longer needed 327 // the loaded bitmap. Yay! 328 mBackground = null; 329 mWallpaperManager.forgetLoadedWallpaper(); 330 } 331 } 332 333 void updateWallpaperLocked() { 334 Throwable exception = null; 335 try { 336 mBackground = mWallpaperManager.getBitmap(); 337 } catch (RuntimeException e) { 338 exception = e; 339 } catch (OutOfMemoryError e) { 340 exception = e; 341 } 342 343 if (exception != null) { 344 mBackground = null; 345 // Note that if we do fail at this, and the default wallpaper can't 346 // be loaded, we will go into a cycle. Don't do a build where the 347 // default wallpaper can't be loaded. 348 Log.w(TAG, "Unable to load wallpaper!", exception); 349 try { 350 mWallpaperManager.clear(); 351 } catch (IOException ex) { 352 // now we're really screwed. 353 Log.w(TAG, "Unable reset to default wallpaper!", ex); 354 } 355 } 356 357 mBackgroundWidth = mBackground != null ? mBackground.getWidth() : 0; 358 mBackgroundHeight = mBackground != null ? mBackground.getHeight() : 0; 359 } 360 361 private void drawWallpaperWithCanvas(SurfaceHolder sh, int w, int h, int x, int y) { 362 Canvas c = sh.lockCanvas(); 363 if (c != null) { 364 try { 365 if (DEBUG) { 366 Log.d(TAG, "Redrawing: x=" + x + ", y=" + y); 367 } 368 369 c.translate(x, y); 370 if (w < 0 || h < 0) { 371 c.save(Canvas.CLIP_SAVE_FLAG); 372 c.clipRect(0, 0, mBackgroundWidth, mBackgroundHeight, Op.DIFFERENCE); 373 c.drawColor(0xff000000); 374 c.restore(); 375 } 376 if (mBackground != null) { 377 c.drawBitmap(mBackground, 0, 0, null); 378 } 379 } finally { 380 sh.unlockCanvasAndPost(c); 381 } 382 } 383 } 384 385 private boolean drawWallpaperWithOpenGL(SurfaceHolder sh, int w, int h, int left, int top) { 386 if (!initGL(sh)) return false; 387 388 final float right = left + mBackgroundWidth; 389 final float bottom = top + mBackgroundHeight; 390 391 final Rect frame = sh.getSurfaceFrame(); 392 393 final Matrix4f ortho = new Matrix4f(); 394 ortho.loadOrtho(0.0f, frame.width(), frame.height(), 0.0f, -1.0f, 1.0f); 395 396 final FloatBuffer triangleVertices = createMesh(left, top, right, bottom); 397 398 final int texture = loadTexture(mBackground); 399 final int program = buildProgram(sSimpleVS, sSimpleFS); 400 401 final int attribPosition = glGetAttribLocation(program, "position"); 402 final int attribTexCoords = glGetAttribLocation(program, "texCoords"); 403 final int uniformTexture = glGetUniformLocation(program, "texture"); 404 final int uniformProjection = glGetUniformLocation(program, "projection"); 405 406 checkGlError(); 407 408 glViewport(0, 0, frame.width(), frame.height()); 409 glBindTexture(GL_TEXTURE_2D, texture); 410 411 glUseProgram(program); 412 glEnableVertexAttribArray(attribPosition); 413 glEnableVertexAttribArray(attribTexCoords); 414 glUniform1i(uniformTexture, 0); 415 glUniformMatrix4fv(uniformProjection, 1, false, ortho.getArray(), 0); 416 417 checkGlError(); 418 419 if (w < 0 || h < 0) { 420 glClearColor(0.0f, 0.0f, 0.0f, 0.0f); 421 glClear(GL_COLOR_BUFFER_BIT); 422 } 423 424 // drawQuad 425 triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET); 426 glVertexAttribPointer(attribPosition, 3, GL_FLOAT, false, 427 TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices); 428 429 triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET); 430 glVertexAttribPointer(attribTexCoords, 3, GL_FLOAT, false, 431 TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices); 432 433 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); 434 435 if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) { 436 throw new RuntimeException("Cannot swap buffers"); 437 } 438 checkEglError(); 439 440 finishGL(); 441 442 return true; 443 } 444 445 private FloatBuffer createMesh(int left, int top, float right, float bottom) { 446 final float[] verticesData = { 447 // X, Y, Z, U, V 448 left, bottom, 0.0f, 0.0f, 1.0f, 449 right, bottom, 0.0f, 1.0f, 1.0f, 450 left, top, 0.0f, 0.0f, 0.0f, 451 right, top, 0.0f, 1.0f, 0.0f, 452 }; 453 454 final int bytes = verticesData.length * FLOAT_SIZE_BYTES; 455 final FloatBuffer triangleVertices = ByteBuffer.allocateDirect(bytes).order( 456 ByteOrder.nativeOrder()).asFloatBuffer(); 457 triangleVertices.put(verticesData).position(0); 458 return triangleVertices; 459 } 460 461 private int loadTexture(Bitmap bitmap) { 462 int[] textures = new int[1]; 463 464 glActiveTexture(GL_TEXTURE0); 465 glGenTextures(1, textures, 0); 466 checkGlError(); 467 468 int texture = textures[0]; 469 glBindTexture(GL_TEXTURE_2D, texture); 470 checkGlError(); 471 472 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 473 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 474 475 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 476 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 477 478 GLUtils.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap, GL_UNSIGNED_BYTE, 0); 479 checkGlError(); 480 481 bitmap.recycle(); 482 483 return texture; 484 } 485 486 private int buildProgram(String vertex, String fragment) { 487 int vertexShader = buildShader(vertex, GL_VERTEX_SHADER); 488 if (vertexShader == 0) return 0; 489 490 int fragmentShader = buildShader(fragment, GL_FRAGMENT_SHADER); 491 if (fragmentShader == 0) return 0; 492 493 int program = glCreateProgram(); 494 glAttachShader(program, vertexShader); 495 checkGlError(); 496 497 glAttachShader(program, fragmentShader); 498 checkGlError(); 499 500 glLinkProgram(program); 501 checkGlError(); 502 503 int[] status = new int[1]; 504 glGetProgramiv(program, GL_LINK_STATUS, status, 0); 505 if (status[0] != GL_TRUE) { 506 String error = glGetProgramInfoLog(program); 507 Log.d(GL_LOG_TAG, "Error while linking program:\n" + error); 508 glDeleteShader(vertexShader); 509 glDeleteShader(fragmentShader); 510 glDeleteProgram(program); 511 return 0; 512 } 513 514 return program; 515 } 516 517 private int buildShader(String source, int type) { 518 int shader = glCreateShader(type); 519 520 glShaderSource(shader, source); 521 checkGlError(); 522 523 glCompileShader(shader); 524 checkGlError(); 525 526 int[] status = new int[1]; 527 glGetShaderiv(shader, GL_COMPILE_STATUS, status, 0); 528 if (status[0] != GL_TRUE) { 529 String error = glGetShaderInfoLog(shader); 530 Log.d(GL_LOG_TAG, "Error while compiling shader:\n" + error); 531 glDeleteShader(shader); 532 return 0; 533 } 534 535 return shader; 536 } 537 538 private void checkEglError() { 539 int error = mEgl.eglGetError(); 540 if (error != EGL_SUCCESS) { 541 Log.w(GL_LOG_TAG, "EGL error = " + GLUtils.getEGLErrorString(error)); 542 } 543 } 544 545 private void checkGlError() { 546 int error = glGetError(); 547 if (error != GL_NO_ERROR) { 548 Log.w(GL_LOG_TAG, "GL error = 0x" + Integer.toHexString(error), new Throwable()); 549 } 550 } 551 552 private void finishGL() { 553 mEgl.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); 554 mEgl.eglDestroySurface(mEglDisplay, mEglSurface); 555 mEgl.eglDestroyContext(mEglDisplay, mEglContext); 556 } 557 558 private boolean initGL(SurfaceHolder surfaceHolder) { 559 mEgl = (EGL10) EGLContext.getEGL(); 560 561 mEglDisplay = mEgl.eglGetDisplay(EGL_DEFAULT_DISPLAY); 562 if (mEglDisplay == EGL_NO_DISPLAY) { 563 throw new RuntimeException("eglGetDisplay failed " + 564 GLUtils.getEGLErrorString(mEgl.eglGetError())); 565 } 566 567 int[] version = new int[2]; 568 if (!mEgl.eglInitialize(mEglDisplay, version)) { 569 throw new RuntimeException("eglInitialize failed " + 570 GLUtils.getEGLErrorString(mEgl.eglGetError())); 571 } 572 573 mEglConfig = chooseEglConfig(); 574 if (mEglConfig == null) { 575 throw new RuntimeException("eglConfig not initialized"); 576 } 577 578 mEglContext = createContext(mEgl, mEglDisplay, mEglConfig); 579 580 mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, null); 581 582 if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) { 583 int error = mEgl.eglGetError(); 584 if (error == EGL_BAD_NATIVE_WINDOW) { 585 Log.e(GL_LOG_TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW."); 586 return false; 587 } 588 throw new RuntimeException("createWindowSurface failed " + 589 GLUtils.getEGLErrorString(error)); 590 } 591 592 if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { 593 throw new RuntimeException("eglMakeCurrent failed " + 594 GLUtils.getEGLErrorString(mEgl.eglGetError())); 595 } 596 597 mGL = mEglContext.getGL(); 598 599 return true; 600 } 601 602 603 EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) { 604 int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; 605 return egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, attrib_list); 606 } 607 608 private EGLConfig chooseEglConfig() { 609 int[] configsCount = new int[1]; 610 EGLConfig[] configs = new EGLConfig[1]; 611 int[] configSpec = getConfig(); 612 if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) { 613 throw new IllegalArgumentException("eglChooseConfig failed " + 614 GLUtils.getEGLErrorString(mEgl.eglGetError())); 615 } else if (configsCount[0] > 0) { 616 return configs[0]; 617 } 618 return null; 619 } 620 621 private int[] getConfig() { 622 return new int[] { 623 EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, 624 EGL_RED_SIZE, 8, 625 EGL_GREEN_SIZE, 8, 626 EGL_BLUE_SIZE, 8, 627 EGL_ALPHA_SIZE, 0, 628 EGL_DEPTH_SIZE, 0, 629 EGL_STENCIL_SIZE, 0, 630 EGL_NONE 631 }; 632 } 633 } 634} 635