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 static android.opengl.GLES20.*;
20import static javax.microedition.khronos.egl.EGL10.*;
21
22import android.app.ActivityManager;
23import android.app.WallpaperManager;
24import android.content.BroadcastReceiver;
25import android.content.ComponentCallbacks2;
26import android.content.Context;
27import android.content.Intent;
28import android.graphics.Bitmap;
29import android.graphics.Canvas;
30import android.graphics.Point;
31import android.graphics.Rect;
32import android.graphics.RectF;
33import android.graphics.Region.Op;
34import android.opengl.GLUtils;
35import android.os.SystemProperties;
36import android.renderscript.Matrix4f;
37import android.service.wallpaper.WallpaperService;
38import android.util.Log;
39import android.view.Display;
40import android.view.MotionEvent;
41import android.view.SurfaceHolder;
42import android.view.WindowManager;
43
44import java.io.IOException;
45import java.nio.ByteBuffer;
46import java.nio.ByteOrder;
47import java.nio.FloatBuffer;
48
49import javax.microedition.khronos.egl.EGL10;
50import javax.microedition.khronos.egl.EGLConfig;
51import javax.microedition.khronos.egl.EGLContext;
52import javax.microedition.khronos.egl.EGLDisplay;
53import javax.microedition.khronos.egl.EGLSurface;
54
55/**
56 * Default built-in wallpaper that simply shows a static image.
57 */
58@SuppressWarnings({"UnusedDeclaration"})
59public class ImageWallpaper extends WallpaperService {
60    private static final String TAG = "ImageWallpaper";
61    private static final String GL_LOG_TAG = "ImageWallpaperGL";
62    private static final boolean DEBUG = false;
63    private static final String PROPERTY_KERNEL_QEMU = "ro.kernel.qemu";
64
65    static final boolean FIXED_SIZED_SURFACE = true;
66    static final boolean USE_OPENGL = true;
67
68    WallpaperManager mWallpaperManager;
69
70    DrawableEngine mEngine;
71
72    boolean mIsHwAccelerated;
73
74    @Override
75    public void onCreate() {
76        super.onCreate();
77        mWallpaperManager = (WallpaperManager) getSystemService(WALLPAPER_SERVICE);
78
79        //noinspection PointlessBooleanExpression,ConstantConditions
80        if (FIXED_SIZED_SURFACE && USE_OPENGL) {
81            if (!isEmulator()) {
82                mIsHwAccelerated = ActivityManager.isHighEndGfx();
83            }
84        }
85    }
86
87    @Override
88    public void onTrimMemory(int level) {
89        if (mEngine != null) {
90            mEngine.trimMemory(level);
91        }
92    }
93
94    private static boolean isEmulator() {
95        return "1".equals(SystemProperties.get(PROPERTY_KERNEL_QEMU, "0"));
96    }
97
98    @Override
99    public Engine onCreateEngine() {
100        mEngine = new DrawableEngine();
101        return mEngine;
102    }
103
104    class DrawableEngine extends Engine {
105        static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
106        static final int EGL_OPENGL_ES2_BIT = 4;
107
108        Bitmap mBackground;
109        int mBackgroundWidth = -1, mBackgroundHeight = -1;
110        int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
111        int mLastRotation = -1;
112        float mXOffset = 0.5f;
113        float mYOffset = 0.5f;
114        float mScale = 1f;
115
116        boolean mVisible = true;
117        boolean mRedrawNeeded;
118        boolean mOffsetsChanged;
119        int mLastXTranslation;
120        int mLastYTranslation;
121
122        private EGL10 mEgl;
123        private EGLDisplay mEglDisplay;
124        private EGLConfig mEglConfig;
125        private EGLContext mEglContext;
126        private EGLSurface mEglSurface;
127
128        private static final String sSimpleVS =
129                "attribute vec4 position;\n" +
130                "attribute vec2 texCoords;\n" +
131                "varying vec2 outTexCoords;\n" +
132                "uniform mat4 projection;\n" +
133                "\nvoid main(void) {\n" +
134                "    outTexCoords = texCoords;\n" +
135                "    gl_Position = projection * position;\n" +
136                "}\n\n";
137        private static final String sSimpleFS =
138                "precision mediump float;\n\n" +
139                "varying vec2 outTexCoords;\n" +
140                "uniform sampler2D texture;\n" +
141                "\nvoid main(void) {\n" +
142                "    gl_FragColor = texture2D(texture, outTexCoords);\n" +
143                "}\n\n";
144
145        private static final int FLOAT_SIZE_BYTES = 4;
146        private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
147        private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
148        private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
149
150        public DrawableEngine() {
151            super();
152            setFixedSizeAllowed(true);
153        }
154
155        public void trimMemory(int level) {
156            if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW &&
157                    mBackground != null) {
158                if (DEBUG) {
159                    Log.d(TAG, "trimMemory");
160                }
161                mBackground.recycle();
162                mBackground = null;
163                mBackgroundWidth = -1;
164                mBackgroundHeight = -1;
165                mWallpaperManager.forgetLoadedWallpaper();
166            }
167        }
168
169        @Override
170        public void onCreate(SurfaceHolder surfaceHolder) {
171            if (DEBUG) {
172                Log.d(TAG, "onCreate");
173            }
174
175            super.onCreate(surfaceHolder);
176
177            updateSurfaceSize(surfaceHolder);
178
179            setOffsetNotificationsEnabled(false);
180        }
181
182        @Override
183        public void onDestroy() {
184            super.onDestroy();
185            mBackground = null;
186            mWallpaperManager.forgetLoadedWallpaper();
187        }
188
189        void updateSurfaceSize(SurfaceHolder surfaceHolder) {
190            Point p = getDefaultDisplaySize();
191
192            // Load background image dimensions, if we haven't saved them yet
193            if (mBackgroundWidth <= 0 || mBackgroundHeight <= 0) {
194                // Need to load the image to get dimensions
195                mWallpaperManager.forgetLoadedWallpaper();
196                updateWallpaperLocked();
197                if (mBackgroundWidth <= 0 || mBackgroundHeight <= 0) {
198                    // Default to the display size if we can't find the dimensions
199                    mBackgroundWidth = p.x;
200                    mBackgroundHeight = p.y;
201                }
202            }
203
204            // Force the wallpaper to cover the screen in both dimensions
205            int surfaceWidth = Math.max(p.x, mBackgroundWidth);
206            int surfaceHeight = Math.max(p.y, mBackgroundHeight);
207
208            // If the surface dimensions haven't changed, then just return
209            final Rect frame = surfaceHolder.getSurfaceFrame();
210            if (frame != null) {
211                final int dw = frame.width();
212                final int dh = frame.height();
213                if (surfaceWidth == dw && surfaceHeight == dh) {
214                    return;
215                }
216            }
217
218            if (FIXED_SIZED_SURFACE) {
219                // Used a fixed size surface, because we are special.  We can do
220                // this because we know the current design of window animations doesn't
221                // cause this to break.
222                surfaceHolder.setFixedSize(surfaceWidth, surfaceHeight);
223            } else {
224                surfaceHolder.setSizeFromLayout();
225            }
226        }
227
228        @Override
229        public void onVisibilityChanged(boolean visible) {
230            if (DEBUG) {
231                Log.d(TAG, "onVisibilityChanged: mVisible, visible=" + mVisible + ", " + visible);
232            }
233
234            if (mVisible != visible) {
235                if (DEBUG) {
236                    Log.d(TAG, "Visibility changed to visible=" + visible);
237                }
238                mVisible = visible;
239                drawFrame();
240            }
241        }
242
243        @Override
244        public void onTouchEvent(MotionEvent event) {
245            super.onTouchEvent(event);
246        }
247
248        @Override
249        public void onOffsetsChanged(float xOffset, float yOffset,
250                float xOffsetStep, float yOffsetStep,
251                int xPixels, int yPixels) {
252            if (DEBUG) {
253                Log.d(TAG, "onOffsetsChanged: xOffset=" + xOffset + ", yOffset=" + yOffset
254                        + ", xOffsetStep=" + xOffsetStep + ", yOffsetStep=" + yOffsetStep
255                        + ", xPixels=" + xPixels + ", yPixels=" + yPixels);
256            }
257
258            if (mXOffset != xOffset || mYOffset != yOffset) {
259                if (DEBUG) {
260                    Log.d(TAG, "Offsets changed to (" + xOffset + "," + yOffset + ").");
261                }
262                mXOffset = xOffset;
263                mYOffset = yOffset;
264                mOffsetsChanged = true;
265            }
266            drawFrame();
267        }
268
269        @Override
270        public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
271            if (DEBUG) {
272                Log.d(TAG, "onSurfaceChanged: width=" + width + ", height=" + height);
273            }
274
275            super.onSurfaceChanged(holder, format, width, height);
276
277            drawFrame();
278        }
279
280        @Override
281        public void onSurfaceDestroyed(SurfaceHolder holder) {
282            super.onSurfaceDestroyed(holder);
283            mLastSurfaceWidth = mLastSurfaceHeight = -1;
284        }
285
286        @Override
287        public void onSurfaceCreated(SurfaceHolder holder) {
288            super.onSurfaceCreated(holder);
289            mLastSurfaceWidth = mLastSurfaceHeight = -1;
290        }
291
292        @Override
293        public void onSurfaceRedrawNeeded(SurfaceHolder holder) {
294            if (DEBUG) {
295                Log.d(TAG, "onSurfaceRedrawNeeded");
296            }
297            super.onSurfaceRedrawNeeded(holder);
298
299            drawFrame();
300        }
301
302        private Point getDefaultDisplaySize() {
303            Point p = new Point();
304            Context c = ImageWallpaper.this.getApplicationContext();
305            WindowManager wm = (WindowManager)c.getSystemService(Context.WINDOW_SERVICE);
306            Display d = wm.getDefaultDisplay();
307            d.getRealSize(p);
308            return p;
309        }
310
311        void drawFrame() {
312            try {
313                int newRotation = ((WindowManager) getSystemService(WINDOW_SERVICE)).
314                        getDefaultDisplay().getRotation();
315
316                // Sometimes a wallpaper is not large enough to cover the screen in one dimension.
317                // Call updateSurfaceSize -- it will only actually do the update if the dimensions
318                // should change
319                if (newRotation != mLastRotation) {
320                    // Update surface size (if necessary)
321                    updateSurfaceSize(getSurfaceHolder());
322                }
323                SurfaceHolder sh = getSurfaceHolder();
324                final Rect frame = sh.getSurfaceFrame();
325                final int dw = frame.width();
326                final int dh = frame.height();
327                boolean surfaceDimensionsChanged = dw != mLastSurfaceWidth
328                        || dh != mLastSurfaceHeight;
329
330                boolean redrawNeeded = surfaceDimensionsChanged || newRotation != mLastRotation;
331                if (!redrawNeeded && !mOffsetsChanged) {
332                    if (DEBUG) {
333                        Log.d(TAG, "Suppressed drawFrame since redraw is not needed "
334                                + "and offsets have not changed.");
335                    }
336                    return;
337                }
338                mLastRotation = newRotation;
339
340                // Load bitmap if it is not yet loaded or if it was loaded at a different size
341                if (mBackground == null || surfaceDimensionsChanged) {
342                    if (DEBUG) {
343                        Log.d(TAG, "Reloading bitmap: mBackground, bgw, bgh, dw, dh = " +
344                                mBackground + ", " +
345                                ((mBackground == null) ? 0 : mBackground.getWidth()) + ", " +
346                                ((mBackground == null) ? 0 : mBackground.getHeight()) + ", " +
347                                dw + ", " + dh);
348                    }
349                    mWallpaperManager.forgetLoadedWallpaper();
350                    updateWallpaperLocked();
351                    if (mBackground == null) {
352                        if (DEBUG) {
353                            Log.d(TAG, "Unable to load bitmap");
354                        }
355                        return;
356                    }
357                    if (DEBUG) {
358                        if (dw != mBackground.getWidth() || dh != mBackground.getHeight()) {
359                            Log.d(TAG, "Surface != bitmap dimensions: surface w/h, bitmap w/h: " +
360                                    dw + ", " + dh + ", " + mBackground.getWidth() + ", " +
361                                    mBackground.getHeight());
362                        }
363                    }
364                }
365
366                // Center the scaled image
367                mScale = Math.max(1f, Math.max(dw / (float) mBackground.getWidth(),
368                        dh / (float) mBackground.getHeight()));
369                final int availw = dw - (int) (mBackground.getWidth() * mScale);
370                final int availh = dh - (int) (mBackground.getHeight() * mScale);
371                int xPixels = availw / 2;
372                int yPixels = availh / 2;
373
374                // Adjust the image for xOffset/yOffset values. If window manager is handling offsets,
375                // mXOffset and mYOffset are set to 0.5f by default and therefore xPixels and yPixels
376                // will remain unchanged
377                final int availwUnscaled = dw - mBackground.getWidth();
378                final int availhUnscaled = dh - mBackground.getHeight();
379                if (availwUnscaled < 0)
380                    xPixels += (int) (availwUnscaled * (mXOffset - .5f) + .5f);
381                if (availhUnscaled < 0)
382                    yPixels += (int) (availhUnscaled * (mYOffset - .5f) + .5f);
383
384                mOffsetsChanged = false;
385                mRedrawNeeded = false;
386                if (surfaceDimensionsChanged) {
387                    mLastSurfaceWidth = dw;
388                    mLastSurfaceHeight = dh;
389                }
390                if (!redrawNeeded && xPixels == mLastXTranslation && yPixels == mLastYTranslation) {
391                    if (DEBUG) {
392                        Log.d(TAG, "Suppressed drawFrame since the image has not "
393                                + "actually moved an integral number of pixels.");
394                    }
395                    return;
396                }
397                mLastXTranslation = xPixels;
398                mLastYTranslation = yPixels;
399
400                if (DEBUG) {
401                    Log.d(TAG, "Redrawing wallpaper");
402                }
403
404                if (mIsHwAccelerated) {
405                    if (!drawWallpaperWithOpenGL(sh, availw, availh, xPixels, yPixels)) {
406                        drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
407                    }
408                } else {
409                    drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
410                }
411            } finally {
412                if (FIXED_SIZED_SURFACE && !mIsHwAccelerated) {
413                    // If the surface is fixed-size, we should only need to
414                    // draw it once and then we'll let the window manager
415                    // position it appropriately.  As such, we no longer needed
416                    // the loaded bitmap.  Yay!
417                    // hw-accelerated renderer retains bitmap for faster rotation
418                    mBackground = null;
419                    mWallpaperManager.forgetLoadedWallpaper();
420                }
421            }
422        }
423
424        private void updateWallpaperLocked() {
425            Throwable exception = null;
426            try {
427                mBackground = null;
428                mBackgroundWidth = -1;
429                mBackgroundHeight = -1;
430                mBackground = mWallpaperManager.getBitmap();
431                mBackgroundWidth = mBackground.getWidth();
432                mBackgroundHeight = mBackground.getHeight();
433            } catch (RuntimeException e) {
434                exception = e;
435            } catch (OutOfMemoryError e) {
436                exception = e;
437            }
438
439            if (exception != null) {
440                mBackground = null;
441                mBackgroundWidth = -1;
442                mBackgroundHeight = -1;
443                // Note that if we do fail at this, and the default wallpaper can't
444                // be loaded, we will go into a cycle.  Don't do a build where the
445                // default wallpaper can't be loaded.
446                Log.w(TAG, "Unable to load wallpaper!", exception);
447                try {
448                    mWallpaperManager.clear();
449                } catch (IOException ex) {
450                    // now we're really screwed.
451                    Log.w(TAG, "Unable reset to default wallpaper!", ex);
452                }
453            }
454        }
455
456        private void drawWallpaperWithCanvas(SurfaceHolder sh, int w, int h, int left, int top) {
457            Canvas c = sh.lockCanvas();
458            if (c != null) {
459                try {
460                    if (DEBUG) {
461                        Log.d(TAG, "Redrawing: left=" + left + ", top=" + top);
462                    }
463
464                    final float right = left + mBackground.getWidth() * mScale;
465                    final float bottom = top + mBackground.getHeight() * mScale;
466                    if (w < 0 || h < 0) {
467                        c.save(Canvas.CLIP_SAVE_FLAG);
468                        c.clipRect(left, top, right, bottom,
469                                Op.DIFFERENCE);
470                        c.drawColor(0xff000000);
471                        c.restore();
472                    }
473                    if (mBackground != null) {
474                        RectF dest = new RectF(left, top, right, bottom);
475                        // add a filter bitmap?
476                        c.drawBitmap(mBackground, null, dest, null);
477                    }
478                } finally {
479                    sh.unlockCanvasAndPost(c);
480                }
481            }
482        }
483
484        private boolean drawWallpaperWithOpenGL(SurfaceHolder sh, int w, int h, int left, int top) {
485            if (!initGL(sh)) return false;
486
487            final float right = left + mBackground.getWidth() * mScale;
488            final float bottom = top + mBackground.getHeight() * mScale;
489
490            final Rect frame = sh.getSurfaceFrame();
491            final Matrix4f ortho = new Matrix4f();
492            ortho.loadOrtho(0.0f, frame.width(), frame.height(), 0.0f, -1.0f, 1.0f);
493
494            final FloatBuffer triangleVertices = createMesh(left, top, right, bottom);
495
496            final int texture = loadTexture(mBackground);
497            final int program = buildProgram(sSimpleVS, sSimpleFS);
498
499            final int attribPosition = glGetAttribLocation(program, "position");
500            final int attribTexCoords = glGetAttribLocation(program, "texCoords");
501            final int uniformTexture = glGetUniformLocation(program, "texture");
502            final int uniformProjection = glGetUniformLocation(program, "projection");
503
504            checkGlError();
505
506            glViewport(0, 0, frame.width(), frame.height());
507            glBindTexture(GL_TEXTURE_2D, texture);
508
509            glUseProgram(program);
510            glEnableVertexAttribArray(attribPosition);
511            glEnableVertexAttribArray(attribTexCoords);
512            glUniform1i(uniformTexture, 0);
513            glUniformMatrix4fv(uniformProjection, 1, false, ortho.getArray(), 0);
514
515            checkGlError();
516
517            if (w > 0 || h > 0) {
518                glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
519                glClear(GL_COLOR_BUFFER_BIT);
520            }
521
522            // drawQuad
523            triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
524            glVertexAttribPointer(attribPosition, 3, GL_FLOAT, false,
525                    TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
526
527            triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
528            glVertexAttribPointer(attribTexCoords, 3, GL_FLOAT, false,
529                    TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
530
531            glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
532
533            boolean status = mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
534            checkEglError();
535
536            finishGL(texture, program);
537
538            return status;
539        }
540
541        private FloatBuffer createMesh(int left, int top, float right, float bottom) {
542            final float[] verticesData = {
543                    // X, Y, Z, U, V
544                     left,  bottom, 0.0f, 0.0f, 1.0f,
545                     right, bottom, 0.0f, 1.0f, 1.0f,
546                     left,  top,    0.0f, 0.0f, 0.0f,
547                     right, top,    0.0f, 1.0f, 0.0f,
548            };
549
550            final int bytes = verticesData.length * FLOAT_SIZE_BYTES;
551            final FloatBuffer triangleVertices = ByteBuffer.allocateDirect(bytes).order(
552                    ByteOrder.nativeOrder()).asFloatBuffer();
553            triangleVertices.put(verticesData).position(0);
554            return triangleVertices;
555        }
556
557        private int loadTexture(Bitmap bitmap) {
558            int[] textures = new int[1];
559
560            glActiveTexture(GL_TEXTURE0);
561            glGenTextures(1, textures, 0);
562            checkGlError();
563
564            int texture = textures[0];
565            glBindTexture(GL_TEXTURE_2D, texture);
566            checkGlError();
567
568            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
569            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
570
571            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
572            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
573
574            GLUtils.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap, GL_UNSIGNED_BYTE, 0);
575            checkGlError();
576
577            return texture;
578        }
579
580        private int buildProgram(String vertex, String fragment) {
581            int vertexShader = buildShader(vertex, GL_VERTEX_SHADER);
582            if (vertexShader == 0) return 0;
583
584            int fragmentShader = buildShader(fragment, GL_FRAGMENT_SHADER);
585            if (fragmentShader == 0) return 0;
586
587            int program = glCreateProgram();
588            glAttachShader(program, vertexShader);
589            glAttachShader(program, fragmentShader);
590            glLinkProgram(program);
591            checkGlError();
592
593            glDeleteShader(vertexShader);
594            glDeleteShader(fragmentShader);
595
596            int[] status = new int[1];
597            glGetProgramiv(program, GL_LINK_STATUS, status, 0);
598            if (status[0] != GL_TRUE) {
599                String error = glGetProgramInfoLog(program);
600                Log.d(GL_LOG_TAG, "Error while linking program:\n" + error);
601                glDeleteProgram(program);
602                return 0;
603            }
604
605            return program;
606        }
607
608        private int buildShader(String source, int type) {
609            int shader = glCreateShader(type);
610
611            glShaderSource(shader, source);
612            checkGlError();
613
614            glCompileShader(shader);
615            checkGlError();
616
617            int[] status = new int[1];
618            glGetShaderiv(shader, GL_COMPILE_STATUS, status, 0);
619            if (status[0] != GL_TRUE) {
620                String error = glGetShaderInfoLog(shader);
621                Log.d(GL_LOG_TAG, "Error while compiling shader:\n" + error);
622                glDeleteShader(shader);
623                return 0;
624            }
625
626            return shader;
627        }
628
629        private void checkEglError() {
630            int error = mEgl.eglGetError();
631            if (error != EGL_SUCCESS) {
632                Log.w(GL_LOG_TAG, "EGL error = " + GLUtils.getEGLErrorString(error));
633            }
634        }
635
636        private void checkGlError() {
637            int error = glGetError();
638            if (error != GL_NO_ERROR) {
639                Log.w(GL_LOG_TAG, "GL error = 0x" + Integer.toHexString(error), new Throwable());
640            }
641        }
642
643        private void finishGL(int texture, int program) {
644            int[] textures = new int[1];
645            textures[0] = texture;
646            glDeleteTextures(1, textures, 0);
647            glDeleteProgram(program);
648            mEgl.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
649            mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
650            mEgl.eglDestroyContext(mEglDisplay, mEglContext);
651            mEgl.eglTerminate(mEglDisplay);
652        }
653
654        private boolean initGL(SurfaceHolder surfaceHolder) {
655            mEgl = (EGL10) EGLContext.getEGL();
656
657            mEglDisplay = mEgl.eglGetDisplay(EGL_DEFAULT_DISPLAY);
658            if (mEglDisplay == EGL_NO_DISPLAY) {
659                throw new RuntimeException("eglGetDisplay failed " +
660                        GLUtils.getEGLErrorString(mEgl.eglGetError()));
661            }
662
663            int[] version = new int[2];
664            if (!mEgl.eglInitialize(mEglDisplay, version)) {
665                throw new RuntimeException("eglInitialize failed " +
666                        GLUtils.getEGLErrorString(mEgl.eglGetError()));
667            }
668
669            mEglConfig = chooseEglConfig();
670            if (mEglConfig == null) {
671                throw new RuntimeException("eglConfig not initialized");
672            }
673
674            mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
675            if (mEglContext == EGL_NO_CONTEXT) {
676                throw new RuntimeException("createContext failed " +
677                        GLUtils.getEGLErrorString(mEgl.eglGetError()));
678            }
679
680            int attribs[] = {
681                EGL_WIDTH, 1,
682                EGL_HEIGHT, 1,
683                EGL_NONE
684            };
685            EGLSurface tmpSurface = mEgl.eglCreatePbufferSurface(mEglDisplay, mEglConfig, attribs);
686            mEgl.eglMakeCurrent(mEglDisplay, tmpSurface, tmpSurface, mEglContext);
687
688            int[] maxSize = new int[1];
689            Rect frame = surfaceHolder.getSurfaceFrame();
690            glGetIntegerv(GL_MAX_TEXTURE_SIZE, maxSize, 0);
691
692            mEgl.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
693            mEgl.eglDestroySurface(mEglDisplay, tmpSurface);
694
695            if(frame.width() > maxSize[0] || frame.height() > maxSize[0]) {
696                mEgl.eglDestroyContext(mEglDisplay, mEglContext);
697                mEgl.eglTerminate(mEglDisplay);
698                Log.e(GL_LOG_TAG, "requested  texture size " +
699                    frame.width() + "x" + frame.height() + " exceeds the support maximum of " +
700                    maxSize[0] + "x" + maxSize[0]);
701                return false;
702            }
703
704            mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, null);
705            if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) {
706                int error = mEgl.eglGetError();
707                if (error == EGL_BAD_NATIVE_WINDOW || error == EGL_BAD_ALLOC) {
708                    Log.e(GL_LOG_TAG, "createWindowSurface returned " +
709                                         GLUtils.getEGLErrorString(error) + ".");
710                    return false;
711                }
712                throw new RuntimeException("createWindowSurface failed " +
713                        GLUtils.getEGLErrorString(error));
714            }
715
716            if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
717                throw new RuntimeException("eglMakeCurrent failed " +
718                        GLUtils.getEGLErrorString(mEgl.eglGetError()));
719            }
720
721            return true;
722        }
723
724
725        EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
726            int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
727            return egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, attrib_list);
728        }
729
730        private EGLConfig chooseEglConfig() {
731            int[] configsCount = new int[1];
732            EGLConfig[] configs = new EGLConfig[1];
733            int[] configSpec = getConfig();
734            if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) {
735                throw new IllegalArgumentException("eglChooseConfig failed " +
736                        GLUtils.getEGLErrorString(mEgl.eglGetError()));
737            } else if (configsCount[0] > 0) {
738                return configs[0];
739            }
740            return null;
741        }
742
743        private int[] getConfig() {
744            return new int[] {
745                    EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
746                    EGL_RED_SIZE, 8,
747                    EGL_GREEN_SIZE, 8,
748                    EGL_BLUE_SIZE, 8,
749                    EGL_ALPHA_SIZE, 0,
750                    EGL_DEPTH_SIZE, 0,
751                    EGL_STENCIL_SIZE, 0,
752                    EGL_CONFIG_CAVEAT, EGL_NONE,
753                    EGL_NONE
754            };
755        }
756    }
757}
758