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