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