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