Home | History | Annotate | Download | only in ui
      1 /*
      2  * Copyright (C) 2010 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 
     17 package com.android.gallery3d.ui;
     18 
     19 import android.annotation.TargetApi;
     20 import android.content.Context;
     21 import android.graphics.Matrix;
     22 import android.graphics.PixelFormat;
     23 import android.opengl.GLSurfaceView;
     24 import android.os.Build;
     25 import android.os.Process;
     26 import android.os.SystemClock;
     27 import android.util.AttributeSet;
     28 import android.view.MotionEvent;
     29 import android.view.SurfaceHolder;
     30 import android.view.View;
     31 
     32 import com.android.gallery3d.R;
     33 import com.android.gallery3d.anim.CanvasAnimation;
     34 import com.android.gallery3d.common.ApiHelper;
     35 import com.android.gallery3d.common.Utils;
     36 import com.android.gallery3d.glrenderer.BasicTexture;
     37 import com.android.gallery3d.glrenderer.GLCanvas;
     38 import com.android.gallery3d.glrenderer.GLES11Canvas;
     39 import com.android.gallery3d.glrenderer.GLES20Canvas;
     40 import com.android.gallery3d.glrenderer.UploadedTexture;
     41 import com.android.gallery3d.util.GalleryUtils;
     42 import com.android.gallery3d.util.MotionEventHelper;
     43 import com.android.gallery3d.util.Profile;
     44 
     45 import java.util.ArrayDeque;
     46 import java.util.ArrayList;
     47 import java.util.concurrent.locks.Condition;
     48 import java.util.concurrent.locks.ReentrantLock;
     49 
     50 import javax.microedition.khronos.egl.EGLConfig;
     51 import javax.microedition.khronos.opengles.GL10;
     52 import javax.microedition.khronos.opengles.GL11;
     53 
     54 // The root component of all <code>GLView</code>s. The rendering is done in GL
     55 // thread while the event handling is done in the main thread.  To synchronize
     56 // the two threads, the entry points of this package need to synchronize on the
     57 // <code>GLRootView</code> instance unless it can be proved that the rendering
     58 // thread won't access the same thing as the method. The entry points include:
     59 // (1) The public methods of HeadUpDisplay
     60 // (2) The public methods of CameraHeadUpDisplay
     61 // (3) The overridden methods in GLRootView.
     62 public class GLRootView extends GLSurfaceView
     63         implements GLSurfaceView.Renderer, GLRoot {
     64     private static final String TAG = "GLRootView";
     65 
     66     private static final boolean DEBUG_FPS = false;
     67     private int mFrameCount = 0;
     68     private long mFrameCountingStart = 0;
     69 
     70     private static final boolean DEBUG_INVALIDATE = false;
     71     private int mInvalidateColor = 0;
     72 
     73     private static final boolean DEBUG_DRAWING_STAT = false;
     74 
     75     private static final boolean DEBUG_PROFILE = false;
     76     private static final boolean DEBUG_PROFILE_SLOW_ONLY = false;
     77 
     78     private static final int FLAG_INITIALIZED = 1;
     79     private static final int FLAG_NEED_LAYOUT = 2;
     80 
     81     private GL11 mGL;
     82     private GLCanvas mCanvas;
     83     private GLView mContentView;
     84 
     85     private OrientationSource mOrientationSource;
     86     // mCompensation is the difference between the UI orientation on GLCanvas
     87     // and the framework orientation. See OrientationManager for details.
     88     private int mCompensation;
     89     // mCompensationMatrix maps the coordinates of touch events. It is kept sync
     90     // with mCompensation.
     91     private Matrix mCompensationMatrix = new Matrix();
     92     private int mDisplayRotation;
     93 
     94     private int mFlags = FLAG_NEED_LAYOUT;
     95     private volatile boolean mRenderRequested = false;
     96 
     97     private final ArrayList<CanvasAnimation> mAnimations =
     98             new ArrayList<CanvasAnimation>();
     99 
    100     private final ArrayDeque<OnGLIdleListener> mIdleListeners =
    101             new ArrayDeque<OnGLIdleListener>();
    102 
    103     private final IdleRunner mIdleRunner = new IdleRunner();
    104 
    105     private final ReentrantLock mRenderLock = new ReentrantLock();
    106     private final Condition mFreezeCondition =
    107             mRenderLock.newCondition();
    108     private boolean mFreeze;
    109 
    110     private long mLastDrawFinishTime;
    111     private boolean mInDownState = false;
    112     private boolean mFirstDraw = true;
    113 
    114     public GLRootView(Context context) {
    115         this(context, null);
    116     }
    117 
    118     public GLRootView(Context context, AttributeSet attrs) {
    119         super(context, attrs);
    120         mFlags |= FLAG_INITIALIZED;
    121         setBackgroundDrawable(null);
    122         setEGLContextClientVersion(ApiHelper.HAS_GLES20_REQUIRED ? 2 : 1);
    123         if (ApiHelper.USE_888_PIXEL_FORMAT) {
    124             setEGLConfigChooser(8, 8, 8, 0, 0, 0);
    125         } else {
    126             setEGLConfigChooser(5, 6, 5, 0, 0, 0);
    127         }
    128         setRenderer(this);
    129         if (ApiHelper.USE_888_PIXEL_FORMAT) {
    130             getHolder().setFormat(PixelFormat.RGB_888);
    131         } else {
    132             getHolder().setFormat(PixelFormat.RGB_565);
    133         }
    134 
    135         // Uncomment this to enable gl error check.
    136         // setDebugFlags(DEBUG_CHECK_GL_ERROR);
    137     }
    138 
    139     @Override
    140     public void registerLaunchedAnimation(CanvasAnimation animation) {
    141         // Register the newly launched animation so that we can set the start
    142         // time more precisely. (Usually, it takes much longer for first
    143         // rendering, so we set the animation start time as the time we
    144         // complete rendering)
    145         mAnimations.add(animation);
    146     }
    147 
    148     @Override
    149     public void addOnGLIdleListener(OnGLIdleListener listener) {
    150         synchronized (mIdleListeners) {
    151             mIdleListeners.addLast(listener);
    152             mIdleRunner.enable();
    153         }
    154     }
    155 
    156     @Override
    157     public void setContentPane(GLView content) {
    158         if (mContentView == content) return;
    159         if (mContentView != null) {
    160             if (mInDownState) {
    161                 long now = SystemClock.uptimeMillis();
    162                 MotionEvent cancelEvent = MotionEvent.obtain(
    163                         now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
    164                 mContentView.dispatchTouchEvent(cancelEvent);
    165                 cancelEvent.recycle();
    166                 mInDownState = false;
    167             }
    168             mContentView.detachFromRoot();
    169             BasicTexture.yieldAllTextures();
    170         }
    171         mContentView = content;
    172         if (content != null) {
    173             content.attachToRoot(this);
    174             requestLayoutContentPane();
    175         }
    176     }
    177 
    178     @Override
    179     public void requestRenderForced() {
    180         superRequestRender();
    181     }
    182 
    183     @Override
    184     public void requestRender() {
    185         if (DEBUG_INVALIDATE) {
    186             StackTraceElement e = Thread.currentThread().getStackTrace()[4];
    187             String caller = e.getFileName() + ":" + e.getLineNumber() + " ";
    188             Log.d(TAG, "invalidate: " + caller);
    189         }
    190         if (mRenderRequested) return;
    191         mRenderRequested = true;
    192         if (ApiHelper.HAS_POST_ON_ANIMATION) {
    193             postOnAnimation(mRequestRenderOnAnimationFrame);
    194         } else {
    195             super.requestRender();
    196         }
    197     }
    198 
    199     private Runnable mRequestRenderOnAnimationFrame = new Runnable() {
    200         @Override
    201         public void run() {
    202             superRequestRender();
    203         }
    204     };
    205 
    206     private void superRequestRender() {
    207         super.requestRender();
    208     }
    209 
    210     @Override
    211     public void requestLayoutContentPane() {
    212         mRenderLock.lock();
    213         try {
    214             if (mContentView == null || (mFlags & FLAG_NEED_LAYOUT) != 0) return;
    215 
    216             // "View" system will invoke onLayout() for initialization(bug ?), we
    217             // have to ignore it since the GLThread is not ready yet.
    218             if ((mFlags & FLAG_INITIALIZED) == 0) return;
    219 
    220             mFlags |= FLAG_NEED_LAYOUT;
    221             requestRender();
    222         } finally {
    223             mRenderLock.unlock();
    224         }
    225     }
    226 
    227     private void layoutContentPane() {
    228         mFlags &= ~FLAG_NEED_LAYOUT;
    229 
    230         int w = getWidth();
    231         int h = getHeight();
    232         int displayRotation = 0;
    233         int compensation = 0;
    234 
    235         // Get the new orientation values
    236         if (mOrientationSource != null) {
    237             displayRotation = mOrientationSource.getDisplayRotation();
    238             compensation = mOrientationSource.getCompensation();
    239         } else {
    240             displayRotation = 0;
    241             compensation = 0;
    242         }
    243 
    244         if (mCompensation != compensation) {
    245             mCompensation = compensation;
    246             if (mCompensation % 180 != 0) {
    247                 mCompensationMatrix.setRotate(mCompensation);
    248                 // move center to origin before rotation
    249                 mCompensationMatrix.preTranslate(-w / 2, -h / 2);
    250                 // align with the new origin after rotation
    251                 mCompensationMatrix.postTranslate(h / 2, w / 2);
    252             } else {
    253                 mCompensationMatrix.setRotate(mCompensation, w / 2, h / 2);
    254             }
    255         }
    256         mDisplayRotation = displayRotation;
    257 
    258         // Do the actual layout.
    259         if (mCompensation % 180 != 0) {
    260             int tmp = w;
    261             w = h;
    262             h = tmp;
    263         }
    264         Log.i(TAG, "layout content pane " + w + "x" + h
    265                 + " (compensation " + mCompensation + ")");
    266         if (mContentView != null && w != 0 && h != 0) {
    267             mContentView.layout(0, 0, w, h);
    268         }
    269         // Uncomment this to dump the view hierarchy.
    270         //mContentView.dumpTree("");
    271     }
    272 
    273     @Override
    274     protected void onLayout(
    275             boolean changed, int left, int top, int right, int bottom) {
    276         if (changed) requestLayoutContentPane();
    277     }
    278 
    279     /**
    280      * Called when the context is created, possibly after automatic destruction.
    281      */
    282     // This is a GLSurfaceView.Renderer callback
    283     @Override
    284     public void onSurfaceCreated(GL10 gl1, EGLConfig config) {
    285         GL11 gl = (GL11) gl1;
    286         if (mGL != null) {
    287             // The GL Object has changed
    288             Log.i(TAG, "GLObject has changed from " + mGL + " to " + gl);
    289         }
    290         mRenderLock.lock();
    291         try {
    292             mGL = gl;
    293             mCanvas = ApiHelper.HAS_GLES20_REQUIRED ? new GLES20Canvas() : new GLES11Canvas(gl);
    294             BasicTexture.invalidateAllTextures();
    295         } finally {
    296             mRenderLock.unlock();
    297         }
    298 
    299         if (DEBUG_FPS || DEBUG_PROFILE) {
    300             setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
    301         } else {
    302             setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    303         }
    304     }
    305 
    306     /**
    307      * Called when the OpenGL surface is recreated without destroying the
    308      * context.
    309      */
    310     // This is a GLSurfaceView.Renderer callback
    311     @Override
    312     public void onSurfaceChanged(GL10 gl1, int width, int height) {
    313         Log.i(TAG, "onSurfaceChanged: " + width + "x" + height
    314                 + ", gl10: " + gl1.toString());
    315         Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);
    316         GalleryUtils.setRenderThread();
    317         if (DEBUG_PROFILE) {
    318             Log.d(TAG, "Start profiling");
    319             Profile.enable(20);  // take a sample every 20ms
    320         }
    321         GL11 gl = (GL11) gl1;
    322         Utils.assertTrue(mGL == gl);
    323 
    324         mCanvas.setSize(width, height);
    325     }
    326 
    327     private void outputFps() {
    328         long now = System.nanoTime();
    329         if (mFrameCountingStart == 0) {
    330             mFrameCountingStart = now;
    331         } else if ((now - mFrameCountingStart) > 1000000000) {
    332             Log.d(TAG, "fps: " + (double) mFrameCount
    333                     * 1000000000 / (now - mFrameCountingStart));
    334             mFrameCountingStart = now;
    335             mFrameCount = 0;
    336         }
    337         ++mFrameCount;
    338     }
    339 
    340     @Override
    341     public void onDrawFrame(GL10 gl) {
    342         AnimationTime.update();
    343         long t0;
    344         if (DEBUG_PROFILE_SLOW_ONLY) {
    345             Profile.hold();
    346             t0 = System.nanoTime();
    347         }
    348         mRenderLock.lock();
    349 
    350         while (mFreeze) {
    351             mFreezeCondition.awaitUninterruptibly();
    352         }
    353 
    354         try {
    355             onDrawFrameLocked(gl);
    356         } finally {
    357             mRenderLock.unlock();
    358         }
    359 
    360         // We put a black cover View in front of the SurfaceView and hide it
    361         // after the first draw. This prevents the SurfaceView being transparent
    362         // before the first draw.
    363         if (mFirstDraw) {
    364             mFirstDraw = false;
    365             post(new Runnable() {
    366                     @Override
    367                     public void run() {
    368                         View root = getRootView();
    369                         View cover = root.findViewById(R.id.gl_root_cover);
    370                         cover.setVisibility(GONE);
    371                     }
    372                 });
    373         }
    374 
    375         if (DEBUG_PROFILE_SLOW_ONLY) {
    376             long t = System.nanoTime();
    377             long durationInMs = (t - mLastDrawFinishTime) / 1000000;
    378             long durationDrawInMs = (t - t0) / 1000000;
    379             mLastDrawFinishTime = t;
    380 
    381             if (durationInMs > 34) {  // 34ms -> we skipped at least 2 frames
    382                 Log.v(TAG, "----- SLOW (" + durationDrawInMs + "/" +
    383                         durationInMs + ") -----");
    384                 Profile.commit();
    385             } else {
    386                 Profile.drop();
    387             }
    388         }
    389     }
    390 
    391     private void onDrawFrameLocked(GL10 gl) {
    392         if (DEBUG_FPS) outputFps();
    393 
    394         // release the unbound textures and deleted buffers.
    395         mCanvas.deleteRecycledResources();
    396 
    397         // reset texture upload limit
    398         UploadedTexture.resetUploadLimit();
    399 
    400         mRenderRequested = false;
    401 
    402         if ((mOrientationSource != null
    403                 && mDisplayRotation != mOrientationSource.getDisplayRotation())
    404                 || (mFlags & FLAG_NEED_LAYOUT) != 0) {
    405             layoutContentPane();
    406         }
    407 
    408         mCanvas.save(GLCanvas.SAVE_FLAG_ALL);
    409         rotateCanvas(-mCompensation);
    410         if (mContentView != null) {
    411            mContentView.render(mCanvas);
    412         } else {
    413             // Make sure we always draw something to prevent displaying garbage
    414             mCanvas.clearBuffer();
    415         }
    416         mCanvas.restore();
    417 
    418         if (!mAnimations.isEmpty()) {
    419             long now = AnimationTime.get();
    420             for (int i = 0, n = mAnimations.size(); i < n; i++) {
    421                 mAnimations.get(i).setStartTime(now);
    422             }
    423             mAnimations.clear();
    424         }
    425 
    426         if (UploadedTexture.uploadLimitReached()) {
    427             requestRender();
    428         }
    429 
    430         synchronized (mIdleListeners) {
    431             if (!mIdleListeners.isEmpty()) mIdleRunner.enable();
    432         }
    433 
    434         if (DEBUG_INVALIDATE) {
    435             mCanvas.fillRect(10, 10, 5, 5, mInvalidateColor);
    436             mInvalidateColor = ~mInvalidateColor;
    437         }
    438 
    439         if (DEBUG_DRAWING_STAT) {
    440             mCanvas.dumpStatisticsAndClear();
    441         }
    442     }
    443 
    444     private void rotateCanvas(int degrees) {
    445         if (degrees == 0) return;
    446         int w = getWidth();
    447         int h = getHeight();
    448         int cx = w / 2;
    449         int cy = h / 2;
    450         mCanvas.translate(cx, cy);
    451         mCanvas.rotate(degrees, 0, 0, 1);
    452         if (degrees % 180 != 0) {
    453             mCanvas.translate(-cy, -cx);
    454         } else {
    455             mCanvas.translate(-cx, -cy);
    456         }
    457     }
    458 
    459     @Override
    460     public boolean dispatchTouchEvent(MotionEvent event) {
    461         if (!isEnabled()) return false;
    462 
    463         int action = event.getAction();
    464         if (action == MotionEvent.ACTION_CANCEL
    465                 || action == MotionEvent.ACTION_UP) {
    466             mInDownState = false;
    467         } else if (!mInDownState && action != MotionEvent.ACTION_DOWN) {
    468             return false;
    469         }
    470 
    471         if (mCompensation != 0) {
    472             event = MotionEventHelper.transformEvent(event, mCompensationMatrix);
    473         }
    474 
    475         mRenderLock.lock();
    476         try {
    477             // If this has been detached from root, we don't need to handle event
    478             boolean handled = mContentView != null
    479                     && mContentView.dispatchTouchEvent(event);
    480             if (action == MotionEvent.ACTION_DOWN && handled) {
    481                 mInDownState = true;
    482             }
    483             return handled;
    484         } finally {
    485             mRenderLock.unlock();
    486         }
    487     }
    488 
    489     private class IdleRunner implements Runnable {
    490         // true if the idle runner is in the queue
    491         private boolean mActive = false;
    492 
    493         @Override
    494         public void run() {
    495             OnGLIdleListener listener;
    496             synchronized (mIdleListeners) {
    497                 mActive = false;
    498                 if (mIdleListeners.isEmpty()) return;
    499                 listener = mIdleListeners.removeFirst();
    500             }
    501             mRenderLock.lock();
    502             boolean keepInQueue;
    503             try {
    504                 keepInQueue = listener.onGLIdle(mCanvas, mRenderRequested);
    505             } finally {
    506                 mRenderLock.unlock();
    507             }
    508             synchronized (mIdleListeners) {
    509                 if (keepInQueue) mIdleListeners.addLast(listener);
    510                 if (!mRenderRequested && !mIdleListeners.isEmpty()) enable();
    511             }
    512         }
    513 
    514         public void enable() {
    515             // Who gets the flag can add it to the queue
    516             if (mActive) return;
    517             mActive = true;
    518             queueEvent(this);
    519         }
    520     }
    521 
    522     @Override
    523     public void lockRenderThread() {
    524         mRenderLock.lock();
    525     }
    526 
    527     @Override
    528     public void unlockRenderThread() {
    529         mRenderLock.unlock();
    530     }
    531 
    532     @Override
    533     public void onPause() {
    534         unfreeze();
    535         super.onPause();
    536         if (DEBUG_PROFILE) {
    537             Log.d(TAG, "Stop profiling");
    538             Profile.disableAll();
    539             Profile.dumpToFile("/sdcard/gallery.prof");
    540             Profile.reset();
    541         }
    542     }
    543 
    544     @Override
    545     public void setOrientationSource(OrientationSource source) {
    546         mOrientationSource = source;
    547     }
    548 
    549     @Override
    550     public int getDisplayRotation() {
    551         return mDisplayRotation;
    552     }
    553 
    554     @Override
    555     public int getCompensation() {
    556         return mCompensation;
    557     }
    558 
    559     @Override
    560     public Matrix getCompensationMatrix() {
    561         return mCompensationMatrix;
    562     }
    563 
    564     @Override
    565     public void freeze() {
    566         mRenderLock.lock();
    567         mFreeze = true;
    568         mRenderLock.unlock();
    569     }
    570 
    571     @Override
    572     public void unfreeze() {
    573         mRenderLock.lock();
    574         mFreeze = false;
    575         mFreezeCondition.signalAll();
    576         mRenderLock.unlock();
    577     }
    578 
    579     @Override
    580     @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    581     public void setLightsOutMode(boolean enabled) {
    582         if (!ApiHelper.HAS_SET_SYSTEM_UI_VISIBILITY) return;
    583 
    584         int flags = 0;
    585         if (enabled) {
    586             flags = STATUS_BAR_HIDDEN;
    587             if (ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_LAYOUT_STABLE) {
    588                 flags |= (SYSTEM_UI_FLAG_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_STABLE);
    589             }
    590         }
    591         setSystemUiVisibility(flags);
    592     }
    593 
    594     // We need to unfreeze in the following methods and in onPause().
    595     // These methods will wait on GLThread. If we have freezed the GLRootView,
    596     // the GLThread will wait on main thread to call unfreeze and cause dead
    597     // lock.
    598     @Override
    599     public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
    600         unfreeze();
    601         super.surfaceChanged(holder, format, w, h);
    602     }
    603 
    604     @Override
    605     public void surfaceCreated(SurfaceHolder holder) {
    606         unfreeze();
    607         super.surfaceCreated(holder);
    608     }
    609 
    610     @Override
    611     public void surfaceDestroyed(SurfaceHolder holder) {
    612         unfreeze();
    613         super.surfaceDestroyed(holder);
    614     }
    615 
    616     @Override
    617     protected void onDetachedFromWindow() {
    618         unfreeze();
    619         super.onDetachedFromWindow();
    620     }
    621 
    622     @Override
    623     protected void finalize() throws Throwable {
    624         try {
    625             unfreeze();
    626         } finally {
    627             super.finalize();
    628         }
    629     }
    630 }
    631