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 
GLRootView(Context context)114     public GLRootView(Context context) {
115         this(context, null);
116     }
117 
GLRootView(Context context, AttributeSet attrs)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
registerLaunchedAnimation(CanvasAnimation animation)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
addOnGLIdleListener(OnGLIdleListener listener)149     public void addOnGLIdleListener(OnGLIdleListener listener) {
150         synchronized (mIdleListeners) {
151             mIdleListeners.addLast(listener);
152             mIdleRunner.enable();
153         }
154     }
155 
156     @Override
setContentPane(GLView content)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
requestRenderForced()179     public void requestRenderForced() {
180         superRequestRender();
181     }
182 
183     @Override
requestRender()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 
superRequestRender()206     private void superRequestRender() {
207         super.requestRender();
208     }
209 
210     @Override
requestLayoutContentPane()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 
layoutContentPane()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
onLayout( boolean changed, int left, int top, int right, int bottom)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
onSurfaceCreated(GL10 gl1, EGLConfig config)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
onSurfaceChanged(GL10 gl1, int width, int height)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 
outputFps()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
onDrawFrame(GL10 gl)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 
onDrawFrameLocked(GL10 gl)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 
rotateCanvas(int degrees)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
dispatchTouchEvent(MotionEvent event)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
run()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 
enable()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
lockRenderThread()523     public void lockRenderThread() {
524         mRenderLock.lock();
525     }
526 
527     @Override
unlockRenderThread()528     public void unlockRenderThread() {
529         mRenderLock.unlock();
530     }
531 
532     @Override
onPause()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
setOrientationSource(OrientationSource source)545     public void setOrientationSource(OrientationSource source) {
546         mOrientationSource = source;
547     }
548 
549     @Override
getDisplayRotation()550     public int getDisplayRotation() {
551         return mDisplayRotation;
552     }
553 
554     @Override
getCompensation()555     public int getCompensation() {
556         return mCompensation;
557     }
558 
559     @Override
getCompensationMatrix()560     public Matrix getCompensationMatrix() {
561         return mCompensationMatrix;
562     }
563 
564     @Override
freeze()565     public void freeze() {
566         mRenderLock.lock();
567         mFreeze = true;
568         mRenderLock.unlock();
569     }
570 
571     @Override
unfreeze()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)
setLightsOutMode(boolean enabled)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
surfaceChanged(SurfaceHolder holder, int format, int w, int h)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
surfaceCreated(SurfaceHolder holder)605     public void surfaceCreated(SurfaceHolder holder) {
606         unfreeze();
607         super.surfaceCreated(holder);
608     }
609 
610     @Override
surfaceDestroyed(SurfaceHolder holder)611     public void surfaceDestroyed(SurfaceHolder holder) {
612         unfreeze();
613         super.surfaceDestroyed(holder);
614     }
615 
616     @Override
onDetachedFromWindow()617     protected void onDetachedFromWindow() {
618         unfreeze();
619         super.onDetachedFromWindow();
620     }
621 
622     @Override
finalize()623     protected void finalize() throws Throwable {
624         try {
625             unfreeze();
626         } finally {
627             super.finalize();
628         }
629     }
630 }
631