2 * Copyright (C) 2009 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.systemui;
19 import android.app.ActivityManager;
20 import android.app.WallpaperManager;
21 import android.content.BroadcastReceiver;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.graphics.Bitmap;
25 import android.graphics.Canvas;
26 import android.graphics.Rect;
27 import android.graphics.Region.Op;
28 import android.opengl.GLUtils;
29 import android.os.SystemProperties;
30 import android.renderscript.Matrix4f;
31 import android.service.wallpaper.WallpaperService;
32 import android.util.Log;
33 import android.view.Display;
34 import android.view.MotionEvent;
35 import android.view.SurfaceHolder;
36 import android.view.WindowManager;
38 import javax.microedition.khronos.egl.EGL10;
39 import javax.microedition.khronos.egl.EGLConfig;
40 import javax.microedition.khronos.egl.EGLContext;
41 import javax.microedition.khronos.egl.EGLDisplay;
42 import javax.microedition.khronos.egl.EGLSurface;
43 import javax.microedition.khronos.opengles.GL;
44 import java.io.IOException;
45 import java.nio.ByteBuffer;
46 import java.nio.ByteOrder;
47 import java.nio.FloatBuffer;
49 import static android.opengl.GLES20.*;
50 import static javax.microedition.khronos.egl.EGL10.*;
53 * Default built-in wallpaper that simply shows a static image.
55 @SuppressWarnings({"UnusedDeclaration"})
56 public 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";
62 static final boolean FIXED_SIZED_SURFACE = true;
63 static final boolean USE_OPENGL = true;
65 WallpaperManager mWallpaperManager;
67 boolean mIsHwAccelerated;
70 public void onCreate() {
72 mWallpaperManager = (WallpaperManager) getSystemService(WALLPAPER_SERVICE);
74 //noinspection PointlessBooleanExpression,ConstantConditions
75 if (FIXED_SIZED_SURFACE && USE_OPENGL) {
77 WindowManager windowManager =
78 (WindowManager) getSystemService(Context.WINDOW_SERVICE);
79 Display display = windowManager.getDefaultDisplay();
80 mIsHwAccelerated = ActivityManager.isHighEndGfx(display);
85 private static boolean isEmulator() {
86 return "1".equals(SystemProperties.get(PROPERTY_KERNEL_QEMU, "0"));
89 public Engine onCreateEngine() {
90 return new DrawableEngine();
93 class DrawableEngine extends Engine {
94 static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
95 static final int EGL_OPENGL_ES2_BIT = 4;
97 private final Object mLock = new Object[0];
99 // TODO: Not currently used, keeping around until we know we don't need it
100 @SuppressWarnings({"UnusedDeclaration"})
101 private WallpaperObserver mReceiver;
104 int mBackgroundWidth = -1, mBackgroundHeight = -1;
108 boolean mVisible = true;
109 boolean mRedrawNeeded;
110 boolean mOffsetsChanged;
111 int mLastXTranslation;
112 int mLastYTranslation;
115 private EGLDisplay mEglDisplay;
116 private EGLConfig mEglConfig;
117 private EGLContext mEglContext;
118 private EGLSurface mEglSurface;
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" +
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" +
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;
143 class WallpaperObserver extends BroadcastReceiver {
144 public void onReceive(Context context, Intent intent) {
146 Log.d(TAG, "onReceive");
149 synchronized (mLock) {
150 mBackgroundWidth = mBackgroundHeight = -1;
152 mRedrawNeeded = true;
158 public DrawableEngine() {
160 setFixedSizeAllowed(true);
164 public void onCreate(SurfaceHolder surfaceHolder) {
166 Log.d(TAG, "onCreate");
169 super.onCreate(surfaceHolder);
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);
177 updateSurfaceSize(surfaceHolder);
179 setOffsetNotificationsEnabled(false);
183 public void onDestroy() {
185 if (mReceiver != null) {
186 unregisterReceiver(mReceiver);
191 public void onDesiredSizeChanged(int desiredWidth, int desiredHeight) {
192 super.onDesiredSizeChanged(desiredWidth, desiredHeight);
193 SurfaceHolder surfaceHolder = getSurfaceHolder();
194 if (surfaceHolder != null) {
195 updateSurfaceSize(surfaceHolder);
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());
206 surfaceHolder.setSizeFromLayout();
211 public void onVisibilityChanged(boolean visible) {
213 Log.d(TAG, "onVisibilityChanged: visible=" + visible);
216 synchronized (mLock) {
217 if (mVisible != visible) {
219 Log.d(TAG, "Visibility changed to visible=" + visible);
228 public void onTouchEvent(MotionEvent event) {
229 super.onTouchEvent(event);
233 public void onOffsetsChanged(float xOffset, float yOffset,
234 float xOffsetStep, float yOffsetStep,
235 int xPixels, int yPixels) {
237 Log.d(TAG, "onOffsetsChanged: xOffset=" + xOffset + ", yOffset=" + yOffset
238 + ", xOffsetStep=" + xOffsetStep + ", yOffsetStep=" + yOffsetStep
239 + ", xPixels=" + xPixels + ", yPixels=" + yPixels);
242 synchronized (mLock) {
243 if (mXOffset != xOffset || mYOffset != yOffset) {
245 Log.d(TAG, "Offsets changed to (" + xOffset + "," + yOffset + ").");
249 mOffsetsChanged = true;
256 public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
258 Log.d(TAG, "onSurfaceChanged: width=" + width + ", height=" + height);
261 super.onSurfaceChanged(holder, format, width, height);
263 synchronized (mLock) {
264 mRedrawNeeded = true;
269 void drawFrameLocked() {
272 Log.d(TAG, "Suppressed drawFrame since wallpaper is not visible.");
276 if (!mRedrawNeeded && !mOffsetsChanged) {
278 Log.d(TAG, "Suppressed drawFrame since redraw is not needed "
279 + "and offsets have not changed.");
284 if (mBackgroundWidth < 0 || mBackgroundHeight < 0) {
285 // If we don't yet know the size of the wallpaper bitmap,
286 // we need to get it now.
287 updateWallpaperLocked();
290 if (mBackground == null) {
291 // If we somehow got to this point after we have last flushed
292 // the wallpaper, well we really need it to draw again. So
293 // seems like we need to reload it. Ouch.
294 updateWallpaperLocked();
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);
306 mOffsetsChanged = false;
307 if (!mRedrawNeeded && xPixels == mLastXTranslation && yPixels == mLastYTranslation) {
309 Log.d(TAG, "Suppressed drawFrame since the image has not "
310 + "actually moved an integral number of pixels.");
314 mRedrawNeeded = false;
315 mLastXTranslation = xPixels;
316 mLastYTranslation = yPixels;
318 if (mIsHwAccelerated) {
319 if (!drawWallpaperWithOpenGL(sh, availw, availh, xPixels, yPixels)) {
320 drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
323 drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
326 if (FIXED_SIZED_SURFACE) {
327 // If the surface is fixed-size, we should only need to
328 // draw it once and then we'll let the window manager
329 // position it appropriately. As such, we no longer needed
330 // the loaded bitmap. Yay!
332 mWallpaperManager.forgetLoadedWallpaper();
336 void updateWallpaperLocked() {
337 Throwable exception = null;
339 mBackground = mWallpaperManager.getBitmap();
340 } catch (RuntimeException e) {
342 } catch (OutOfMemoryError e) {
346 if (exception != null) {
348 // Note that if we do fail at this, and the default wallpaper can't
349 // be loaded, we will go into a cycle. Don't do a build where the
350 // default wallpaper can't be loaded.
351 Log.w(TAG, "Unable to load wallpaper!", exception);
353 mWallpaperManager.clear();
354 } catch (IOException ex) {
355 // now we're really screwed.
356 Log.w(TAG, "Unable reset to default wallpaper!", ex);
360 mBackgroundWidth = mBackground != null ? mBackground.getWidth() : 0;
361 mBackgroundHeight = mBackground != null ? mBackground.getHeight() : 0;
364 private void drawWallpaperWithCanvas(SurfaceHolder sh, int w, int h, int x, int y) {
365 Canvas c = sh.lockCanvas();
369 Log.d(TAG, "Redrawing: x=" + x + ", y=" + y);
373 if (w < 0 || h < 0) {
374 c.save(Canvas.CLIP_SAVE_FLAG);
375 c.clipRect(0, 0, mBackgroundWidth, mBackgroundHeight, Op.DIFFERENCE);
376 c.drawColor(0xff000000);
379 if (mBackground != null) {
380 c.drawBitmap(mBackground, 0, 0, null);
383 sh.unlockCanvasAndPost(c);
388 private boolean drawWallpaperWithOpenGL(SurfaceHolder sh, int w, int h, int left, int top) {
389 if (!initGL(sh)) return false;
391 final float right = left + mBackgroundWidth;
392 final float bottom = top + mBackgroundHeight;
394 final Rect frame = sh.getSurfaceFrame();
396 final Matrix4f ortho = new Matrix4f();
397 ortho.loadOrtho(0.0f, frame.width(), frame.height(), 0.0f, -1.0f, 1.0f);
399 final FloatBuffer triangleVertices = createMesh(left, top, right, bottom);
401 final int texture = loadTexture(mBackground);
402 final int program = buildProgram(sSimpleVS, sSimpleFS);
404 final int attribPosition = glGetAttribLocation(program, "position");
405 final int attribTexCoords = glGetAttribLocation(program, "texCoords");
406 final int uniformTexture = glGetUniformLocation(program, "texture");
407 final int uniformProjection = glGetUniformLocation(program, "projection");
411 glViewport(0, 0, frame.width(), frame.height());
412 glBindTexture(GL_TEXTURE_2D, texture);
414 glUseProgram(program);
415 glEnableVertexAttribArray(attribPosition);
416 glEnableVertexAttribArray(attribTexCoords);
417 glUniform1i(uniformTexture, 0);
418 glUniformMatrix4fv(uniformProjection, 1, false, ortho.getArray(), 0);
422 if (w < 0 || h < 0) {
423 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
424 glClear(GL_COLOR_BUFFER_BIT);
428 triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
429 glVertexAttribPointer(attribPosition, 3, GL_FLOAT, false,
430 TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
432 triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
433 glVertexAttribPointer(attribTexCoords, 3, GL_FLOAT, false,
434 TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
436 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
438 if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
439 throw new RuntimeException("Cannot swap buffers");
448 private FloatBuffer createMesh(int left, int top, float right, float bottom) {
449 final float[] verticesData = {
451 left, bottom, 0.0f, 0.0f, 1.0f,
452 right, bottom, 0.0f, 1.0f, 1.0f,
453 left, top, 0.0f, 0.0f, 0.0f,
454 right, top, 0.0f, 1.0f, 0.0f,
457 final int bytes = verticesData.length * FLOAT_SIZE_BYTES;
458 final FloatBuffer triangleVertices = ByteBuffer.allocateDirect(bytes).order(
459 ByteOrder.nativeOrder()).asFloatBuffer();
460 triangleVertices.put(verticesData).position(0);
461 return triangleVertices;
464 private int loadTexture(Bitmap bitmap) {
465 int[] textures = new int[1];
467 glActiveTexture(GL_TEXTURE0);
468 glGenTextures(1, textures, 0);
471 int texture = textures[0];
472 glBindTexture(GL_TEXTURE_2D, texture);
475 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
476 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
478 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
479 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
481 GLUtils.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap, GL_UNSIGNED_BYTE, 0);
489 private int buildProgram(String vertex, String fragment) {
490 int vertexShader = buildShader(vertex, GL_VERTEX_SHADER);
491 if (vertexShader == 0) return 0;
493 int fragmentShader = buildShader(fragment, GL_FRAGMENT_SHADER);
494 if (fragmentShader == 0) return 0;
496 int program = glCreateProgram();
497 glAttachShader(program, vertexShader);
500 glAttachShader(program, fragmentShader);
503 glLinkProgram(program);
506 int[] status = new int[1];
507 glGetProgramiv(program, GL_LINK_STATUS, status, 0);
508 if (status[0] != GL_TRUE) {
509 String error = glGetProgramInfoLog(program);
510 Log.d(GL_LOG_TAG, "Error while linking program:\n" + error);
511 glDeleteShader(vertexShader);
512 glDeleteShader(fragmentShader);
513 glDeleteProgram(program);
520 private int buildShader(String source, int type) {
521 int shader = glCreateShader(type);
523 glShaderSource(shader, source);
526 glCompileShader(shader);
529 int[] status = new int[1];
530 glGetShaderiv(shader, GL_COMPILE_STATUS, status, 0);
531 if (status[0] != GL_TRUE) {
532 String error = glGetShaderInfoLog(shader);
533 Log.d(GL_LOG_TAG, "Error while compiling shader:\n" + error);
534 glDeleteShader(shader);
541 private void checkEglError() {
542 int error = mEgl.eglGetError();
543 if (error != EGL_SUCCESS) {
544 Log.w(GL_LOG_TAG, "EGL error = " + GLUtils.getEGLErrorString(error));
548 private void checkGlError() {
549 int error = glGetError();
550 if (error != GL_NO_ERROR) {
551 Log.w(GL_LOG_TAG, "GL error = 0x" + Integer.toHexString(error), new Throwable());
555 private void finishGL() {
556 mEgl.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
557 mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
558 mEgl.eglDestroyContext(mEglDisplay, mEglContext);
561 private boolean initGL(SurfaceHolder surfaceHolder) {
562 mEgl = (EGL10) EGLContext.getEGL();
564 mEglDisplay = mEgl.eglGetDisplay(EGL_DEFAULT_DISPLAY);
565 if (mEglDisplay == EGL_NO_DISPLAY) {
566 throw new RuntimeException("eglGetDisplay failed " +
567 GLUtils.getEGLErrorString(mEgl.eglGetError()));
570 int[] version = new int[2];
571 if (!mEgl.eglInitialize(mEglDisplay, version)) {
572 throw new RuntimeException("eglInitialize failed " +
573 GLUtils.getEGLErrorString(mEgl.eglGetError()));
576 mEglConfig = chooseEglConfig();
577 if (mEglConfig == null) {
578 throw new RuntimeException("eglConfig not initialized");
581 mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
583 mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, null);
585 if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) {
586 int error = mEgl.eglGetError();
587 if (error == EGL_BAD_NATIVE_WINDOW) {
588 Log.e(GL_LOG_TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
591 throw new RuntimeException("createWindowSurface failed " +
592 GLUtils.getEGLErrorString(error));
595 if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
596 throw new RuntimeException("eglMakeCurrent failed " +
597 GLUtils.getEGLErrorString(mEgl.eglGetError()));
600 mGL = mEglContext.getGL();
606 EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
607 int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
608 return egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, attrib_list);
611 private EGLConfig chooseEglConfig() {
612 int[] configsCount = new int[1];
613 EGLConfig[] configs = new EGLConfig[1];
614 int[] configSpec = getConfig();
615 if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) {
616 throw new IllegalArgumentException("eglChooseConfig failed " +
617 GLUtils.getEGLErrorString(mEgl.eglGetError()));
618 } else if (configsCount[0] > 0) {
624 private int[] getConfig() {
626 EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,