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         }
    413         mCanvas.restore();
    414 
    415         if (!mAnimations.isEmpty()) {
    416             long now = AnimationTime.get();
    417             for (int i = 0, n = mAnimations.size(); i < n; i++) {
    418                 mAnimations.get(i).setStartTime(now);
    419             }
    420             mAnimations.clear();
    421         }
    422 
    423         if (UploadedTexture.uploadLimitReached()) {
    424             requestRender();
    425         }
    426 
    427         synchronized (mIdleListeners) {
    428             if (!mIdleListeners.isEmpty()) mIdleRunner.enable();
    429         }
    430 
    431         if (DEBUG_INVALIDATE) {
    432             mCanvas.fillRect(10, 10, 5, 5, mInvalidateColor);
    433             mInvalidateColor = ~mInvalidateColor;
    434         }
    435 
    436         if (DEBUG_DRAWING_STAT) {
    437             mCanvas.dumpStatisticsAndClear();
    438         }
    439     }
    440 
    441     private void rotateCanvas(int degrees) {
    442         if (degrees == 0) return;
    443         int w = getWidth();
    444         int h = getHeight();
    445         int cx = w / 2;
    446         int cy = h / 2;
    447         mCanvas.translate(cx, cy);
    448         mCanvas.rotate(degrees, 0, 0, 1);
    449         if (degrees % 180 != 0) {
    450             mCanvas.translate(-cy, -cx);
    451         } else {
    452             mCanvas.translate(-cx, -cy);
    453         }
    454     }
    455 
    456     @Override
    457     public boolean dispatchTouchEvent(MotionEvent event) {
    458         if (!isEnabled()) return false;
    459 
    460         int action = event.getAction();
    461         if (action == MotionEvent.ACTION_CANCEL
    462                 || action == MotionEvent.ACTION_UP) {
    463             mInDownState = false;
    464         } else if (!mInDownState && action != MotionEvent.ACTION_DOWN) {
    465             return false;
    466         }
    467 
    468         if (mCompensation != 0) {
    469             event = MotionEventHelper.transformEvent(event, mCompensationMatrix);
    470         }
    471 
    472         mRenderLock.lock();
    473         try {
    474             // If this has been detached from root, we don't need to handle event
    475             boolean handled = mContentView != null
    476                     && mContentView.dispatchTouchEvent(event);
    477             if (action == MotionEvent.ACTION_DOWN && handled) {
    478                 mInDownState = true;
    479             }
    480             return handled;
    481         } finally {
    482             mRenderLock.unlock();
    483         }
    484     }
    485 
    486     private class IdleRunner implements Runnable {
    487         // true if the idle runner is in the queue
    488         private boolean mActive = false;
    489 
    490         @Override
    491         public void run() {
    492             OnGLIdleListener listener;
    493             synchronized (mIdleListeners) {
    494                 mActive = false;
    495                 if (mIdleListeners.isEmpty()) return;
    496                 listener = mIdleListeners.removeFirst();
    497             }
    498             mRenderLock.lock();
    499             boolean keepInQueue;
    500             try {
    501                 keepInQueue = listener.onGLIdle(mCanvas, mRenderRequested);
    502             } finally {
    503                 mRenderLock.unlock();
    504             }
    505             synchronized (mIdleListeners) {
    506                 if (keepInQueue) mIdleListeners.addLast(listener);
    507                 if (!mRenderRequested && !mIdleListeners.isEmpty()) enable();
    508             }
    509         }
    510 
    511         public void enable() {
    512             // Who gets the flag can add it to the queue
    513             if (mActive) return;
    514             mActive = true;
    515             queueEvent(this);
    516         }
    517     }
    518 
    519     @Override
    520     public void lockRenderThread() {
    521         mRenderLock.lock();
    522     }
    523 
    524     @Override
    525     public void unlockRenderThread() {
    526         mRenderLock.unlock();
    527     }
    528 
    529     @Override
    530     public void onPause() {
    531         unfreeze();
    532         super.onPause();
    533         if (DEBUG_PROFILE) {
    534             Log.d(TAG, "Stop profiling");
    535             Profile.disableAll();
    536             Profile.dumpToFile("/sdcard/gallery.prof");
    537             Profile.reset();
    538         }
    539     }
    540 
    541     @Override
    542     public void setOrientationSource(OrientationSource source) {
    543         mOrientationSource = source;
    544     }
    545 
    546     @Override
    547     public int getDisplayRotation() {
    548         return mDisplayRotation;
    549     }
    550 
    551     @Override
    552     public int getCompensation() {
    553         return mCompensation;
    554     }
    555 
    556     @Override
    557     public Matrix getCompensationMatrix() {
    558         return mCompensationMatrix;
    559     }
    560 
    561     @Override
    562     public void freeze() {
    563         mRenderLock.lock();
    564         mFreeze = true;
    565         mRenderLock.unlock();
    566     }
    567 
    568     @Override
    569     public void unfreeze() {
    570         mRenderLock.lock();
    571         mFreeze = false;
    572         mFreezeCondition.signalAll();
    573         mRenderLock.unlock();
    574     }
    575 
    576     @Override
    577     @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    578     public void setLightsOutMode(boolean enabled) {
    579         if (!ApiHelper.HAS_SET_SYSTEM_UI_VISIBILITY) return;
    580 
    581         int flags = 0;
    582         if (enabled) {
    583             flags = STATUS_BAR_HIDDEN;
    584             if (ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_LAYOUT_STABLE) {
    585                 flags |= (SYSTEM_UI_FLAG_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_STABLE);
    586             }
    587         }
    588         setSystemUiVisibility(flags);
    589     }
    590 
    591     // We need to unfreeze in the following methods and in onPause().
    592     // These methods will wait on GLThread. If we have freezed the GLRootView,
    593     // the GLThread will wait on main thread to call unfreeze and cause dead
    594     // lock.
    595     @Override
    596     public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
    597         unfreeze();
    598         super.surfaceChanged(holder, format, w, h);
    599     }
    600 
    601     @Override
    602     public void surfaceCreated(SurfaceHolder holder) {
    603         unfreeze();
    604         super.surfaceCreated(holder);
    605     }
    606 
    607     @Override
    608     public void surfaceDestroyed(SurfaceHolder holder) {
    609         unfreeze();
    610         super.surfaceDestroyed(holder);
    611     }
    612 
    613     @Override
    614     protected void onDetachedFromWindow() {
    615         unfreeze();
    616         super.onDetachedFromWindow();
    617     }
    618 
    619     @Override
    620     protected void finalize() throws Throwable {
    621         try {
    622             unfreeze();
    623         } finally {
    624             super.finalize();
    625         }
    626     }
    627 }
    628