1 /*
2  * Copyright (C) 2012 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.camera;
18 
19 import android.annotation.TargetApi;
20 import android.graphics.SurfaceTexture;
21 import android.opengl.Matrix;
22 import android.util.Log;
23 
24 import com.android.gallery3d.common.ApiHelper;
25 import com.android.gallery3d.glrenderer.GLCanvas;
26 import com.android.gallery3d.glrenderer.RawTexture;
27 import com.android.gallery3d.ui.SurfaceTextureScreenNail;
28 
29 /*
30  * This is a ScreenNail which can display camera's preview.
31  */
32 @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
33 public class CameraScreenNail extends SurfaceTextureScreenNail {
34     private static final String TAG = "CAM_ScreenNail";
35     private static final int ANIM_NONE = 0;
36     // Capture animation is about to start.
37     private static final int ANIM_CAPTURE_START = 1;
38     // Capture animation is running.
39     private static final int ANIM_CAPTURE_RUNNING = 2;
40     // Switch camera animation needs to copy texture.
41     private static final int ANIM_SWITCH_COPY_TEXTURE = 3;
42     // Switch camera animation shows the initial feedback by darkening the
43     // preview.
44     private static final int ANIM_SWITCH_DARK_PREVIEW = 4;
45     // Switch camera animation is waiting for the first frame.
46     private static final int ANIM_SWITCH_WAITING_FIRST_FRAME = 5;
47     // Switch camera animation is about to start.
48     private static final int ANIM_SWITCH_START = 6;
49     // Switch camera animation is running.
50     private static final int ANIM_SWITCH_RUNNING = 7;
51 
52     private boolean mVisible;
53     // True if first onFrameAvailable has been called. If screen nail is drawn
54     // too early, it will be all white.
55     private boolean mFirstFrameArrived;
56     private Listener mListener;
57     private final float[] mTextureTransformMatrix = new float[16];
58 
59     // Animation.
60     private CaptureAnimManager mCaptureAnimManager = new CaptureAnimManager();
61     private SwitchAnimManager mSwitchAnimManager = new SwitchAnimManager();
62     private int mAnimState = ANIM_NONE;
63     private RawTexture mAnimTexture;
64     // Some methods are called by GL thread and some are called by main thread.
65     // This protects mAnimState, mVisible, and surface texture. This also makes
66     // sure some code are atomic. For example, requestRender and setting
67     // mAnimState.
68     private Object mLock = new Object();
69 
70     private OnFrameDrawnListener mOneTimeFrameDrawnListener;
71     private int mRenderWidth;
72     private int mRenderHeight;
73     // This represents the scaled, uncropped size of the texture
74     // Needed for FaceView
75     private int mUncroppedRenderWidth;
76     private int mUncroppedRenderHeight;
77     private float mScaleX = 1f, mScaleY = 1f;
78     private boolean mFullScreen;
79     private boolean mEnableAspectRatioClamping = false;
80     private boolean mAcquireTexture = false;
81     private final DrawClient mDefaultDraw = new DrawClient() {
82         @Override
83         public void onDraw(GLCanvas canvas, int x, int y, int width, int height) {
84             CameraScreenNail.super.draw(canvas, x, y, width, height);
85         }
86 
87         @Override
88         public boolean requiresSurfaceTexture() {
89             return true;
90         }
91     };
92     private DrawClient mDraw = mDefaultDraw;
93 
94     public interface Listener {
requestRender()95         void requestRender();
96         // Preview has been copied to a texture.
onPreviewTextureCopied()97         void onPreviewTextureCopied();
98 
onCaptureTextureCopied()99         void onCaptureTextureCopied();
100     }
101 
102     public interface OnFrameDrawnListener {
onFrameDrawn(CameraScreenNail c)103         void onFrameDrawn(CameraScreenNail c);
104     }
105 
106     public interface DrawClient {
onDraw(GLCanvas canvas, int x, int y, int width, int height)107         void onDraw(GLCanvas canvas, int x, int y, int width, int height);
108 
requiresSurfaceTexture()109         boolean requiresSurfaceTexture();
110     }
111 
CameraScreenNail(Listener listener)112     public CameraScreenNail(Listener listener) {
113         mListener = listener;
114     }
115 
setFullScreen(boolean full)116     public void setFullScreen(boolean full) {
117         synchronized (mLock) {
118             mFullScreen = full;
119         }
120     }
121 
122     /**
123      * returns the uncropped, but scaled, width of the rendered texture
124      */
getUncroppedRenderWidth()125     public int getUncroppedRenderWidth() {
126         return mUncroppedRenderWidth;
127     }
128 
129     /**
130      * returns the uncropped, but scaled, width of the rendered texture
131      */
getUncroppedRenderHeight()132     public int getUncroppedRenderHeight() {
133         return mUncroppedRenderHeight;
134     }
135 
136     @Override
getWidth()137     public int getWidth() {
138         return mEnableAspectRatioClamping ? mRenderWidth : getTextureWidth();
139     }
140 
141     @Override
getHeight()142     public int getHeight() {
143         return mEnableAspectRatioClamping ? mRenderHeight : getTextureHeight();
144     }
145 
getTextureWidth()146     private int getTextureWidth() {
147         return super.getWidth();
148     }
149 
getTextureHeight()150     private int getTextureHeight() {
151         return super.getHeight();
152     }
153 
154     @Override
setSize(int w, int h)155     public void setSize(int w, int h) {
156         super.setSize(w,  h);
157         mEnableAspectRatioClamping = false;
158         if (mRenderWidth == 0) {
159             mRenderWidth = w;
160             mRenderHeight = h;
161         }
162         updateRenderSize();
163     }
164 
165     /**
166      * Tells the ScreenNail to override the default aspect ratio scaling
167      * and instead perform custom scaling to basically do a centerCrop instead
168      * of the default centerInside
169      *
170      * Note that calls to setSize will disable this
171      */
enableAspectRatioClamping()172     public void enableAspectRatioClamping() {
173         mEnableAspectRatioClamping = true;
174         updateRenderSize();
175     }
176 
setPreviewLayoutSize(int w, int h)177     private void setPreviewLayoutSize(int w, int h) {
178         Log.i(TAG, "preview layout size: "+w+"/"+h);
179         mRenderWidth = w;
180         mRenderHeight = h;
181         updateRenderSize();
182     }
183 
updateRenderSize()184     private void updateRenderSize() {
185         if (!mEnableAspectRatioClamping) {
186             mScaleX = mScaleY = 1f;
187             mUncroppedRenderWidth = getTextureWidth();
188             mUncroppedRenderHeight = getTextureHeight();
189             Log.i(TAG, "aspect ratio clamping disabled");
190             return;
191         }
192 
193         float aspectRatio;
194         if (getTextureWidth() > getTextureHeight()) {
195             aspectRatio = (float) getTextureWidth() / (float) getTextureHeight();
196         } else {
197             aspectRatio = (float) getTextureHeight() / (float) getTextureWidth();
198         }
199         float scaledTextureWidth, scaledTextureHeight;
200         if (mRenderWidth > mRenderHeight) {
201             scaledTextureWidth = Math.max(mRenderWidth,
202                     (int) (mRenderHeight * aspectRatio));
203             scaledTextureHeight = Math.max(mRenderHeight,
204                     (int)(mRenderWidth / aspectRatio));
205         } else {
206             scaledTextureWidth = Math.max(mRenderWidth,
207                     (int) (mRenderHeight / aspectRatio));
208             scaledTextureHeight = Math.max(mRenderHeight,
209                     (int) (mRenderWidth * aspectRatio));
210         }
211         mScaleX = mRenderWidth / scaledTextureWidth;
212         mScaleY = mRenderHeight / scaledTextureHeight;
213         mUncroppedRenderWidth = Math.round(scaledTextureWidth);
214         mUncroppedRenderHeight = Math.round(scaledTextureHeight);
215         Log.i(TAG, "aspect ratio clamping enabled, surfaceTexture scale: " + mScaleX + ", " + mScaleY);
216     }
217 
acquireSurfaceTexture()218     public void acquireSurfaceTexture() {
219         synchronized (mLock) {
220             mFirstFrameArrived = false;
221             mAnimTexture = new RawTexture(getTextureWidth(), getTextureHeight(), true);
222             mAcquireTexture = true;
223         }
224         mListener.requestRender();
225     }
226 
227     @Override
releaseSurfaceTexture()228     public void releaseSurfaceTexture() {
229         synchronized (mLock) {
230             if (mAcquireTexture) {
231                 mAcquireTexture = false;
232                 mLock.notifyAll();
233             } else {
234                 if (super.getSurfaceTexture() != null) {
235                     super.releaseSurfaceTexture();
236                 }
237                 mAnimState = ANIM_NONE; // stop the animation
238             }
239         }
240     }
241 
copyTexture()242     public void copyTexture() {
243         synchronized (mLock) {
244             mListener.requestRender();
245             mAnimState = ANIM_SWITCH_COPY_TEXTURE;
246         }
247     }
248 
animateSwitchCamera()249     public void animateSwitchCamera() {
250         Log.v(TAG, "animateSwitchCamera");
251         synchronized (mLock) {
252             if (mAnimState == ANIM_SWITCH_DARK_PREVIEW) {
253                 // Do not request render here because camera has been just
254                 // started. We do not want to draw black frames.
255                 mAnimState = ANIM_SWITCH_WAITING_FIRST_FRAME;
256             }
257         }
258     }
259 
animateCapture(int displayRotation)260     public void animateCapture(int displayRotation) {
261         synchronized (mLock) {
262             mCaptureAnimManager.setOrientation(displayRotation);
263             mCaptureAnimManager.animateFlashAndSlide();
264             mListener.requestRender();
265             mAnimState = ANIM_CAPTURE_START;
266         }
267     }
268 
getAnimationTexture()269     public RawTexture getAnimationTexture() {
270         return mAnimTexture;
271     }
272 
animateFlash(int displayRotation)273     public void animateFlash(int displayRotation) {
274         synchronized (mLock) {
275             mCaptureAnimManager.setOrientation(displayRotation);
276             mCaptureAnimManager.animateFlash();
277             mListener.requestRender();
278             mAnimState = ANIM_CAPTURE_START;
279         }
280     }
281 
animateSlide()282     public void animateSlide() {
283         synchronized (mLock) {
284             // Ignore the case where animateFlash is skipped but animateSlide is called
285             // e.g. Double tap shutter and immediately swipe to gallery, and quickly swipe back
286             // to camera. This case only happens in monkey tests, not applicable to normal
287             // human beings.
288             if (mAnimState != ANIM_CAPTURE_RUNNING) {
289                 Log.v(TAG, "Cannot animateSlide outside of animateCapture!"
290                         + " Animation state = " + mAnimState);
291                 return;
292             }
293             mCaptureAnimManager.animateSlide();
294             mListener.requestRender();
295         }
296     }
297 
callbackIfNeeded()298     private void callbackIfNeeded() {
299         if (mOneTimeFrameDrawnListener != null) {
300             mOneTimeFrameDrawnListener.onFrameDrawn(this);
301             mOneTimeFrameDrawnListener = null;
302         }
303     }
304 
305     @Override
updateTransformMatrix(float[] matrix)306     protected void updateTransformMatrix(float[] matrix) {
307         super.updateTransformMatrix(matrix);
308         Matrix.translateM(matrix, 0, .5f, .5f, 0);
309         Matrix.scaleM(matrix, 0, mScaleX, mScaleY, 1f);
310         Matrix.translateM(matrix, 0, -.5f, -.5f, 0);
311     }
312 
directDraw(GLCanvas canvas, int x, int y, int width, int height)313     public void directDraw(GLCanvas canvas, int x, int y, int width, int height) {
314         DrawClient draw;
315         synchronized (mLock) {
316             draw = mDraw;
317         }
318         draw.onDraw(canvas, x, y, width, height);
319     }
320 
setDraw(DrawClient draw)321     public void setDraw(DrawClient draw) {
322         synchronized (mLock) {
323             if (draw == null) {
324                 mDraw = mDefaultDraw;
325             } else {
326                 mDraw = draw;
327             }
328         }
329         mListener.requestRender();
330     }
331 
332     @Override
draw(GLCanvas canvas, int x, int y, int width, int height)333     public void draw(GLCanvas canvas, int x, int y, int width, int height) {
334         synchronized (mLock) {
335             allocateTextureIfRequested(canvas);
336             if (!mVisible) mVisible = true;
337             SurfaceTexture surfaceTexture = getSurfaceTexture();
338             if (mDraw.requiresSurfaceTexture() && (surfaceTexture == null || !mFirstFrameArrived)) {
339                 return;
340             }
341 
342             switch (mAnimState) {
343                 case ANIM_NONE:
344                     directDraw(canvas, x, y, width, height);
345                     break;
346                 case ANIM_SWITCH_COPY_TEXTURE:
347                     copyPreviewTexture(canvas);
348                     mSwitchAnimManager.setReviewDrawingSize(width, height);
349                     mListener.onPreviewTextureCopied();
350                     mAnimState = ANIM_SWITCH_DARK_PREVIEW;
351                     // The texture is ready. Fall through to draw darkened
352                     // preview.
353                 case ANIM_SWITCH_DARK_PREVIEW:
354                 case ANIM_SWITCH_WAITING_FIRST_FRAME:
355                     // Consume the frame. If the buffers are full,
356                     // onFrameAvailable will not be called. Animation state
357                     // relies on onFrameAvailable.
358                     surfaceTexture.updateTexImage();
359                     mSwitchAnimManager.drawDarkPreview(canvas, x, y, width,
360                             height, mAnimTexture);
361                     break;
362                 case ANIM_SWITCH_START:
363                     mSwitchAnimManager.startAnimation();
364                     mAnimState = ANIM_SWITCH_RUNNING;
365                     break;
366                 case ANIM_CAPTURE_START:
367                     copyPreviewTexture(canvas);
368                     mListener.onCaptureTextureCopied();
369                     mCaptureAnimManager.startAnimation(x, y, width, height);
370                     mAnimState = ANIM_CAPTURE_RUNNING;
371                     break;
372             }
373 
374             if (mAnimState == ANIM_CAPTURE_RUNNING || mAnimState == ANIM_SWITCH_RUNNING) {
375                 boolean drawn;
376                 if (mAnimState == ANIM_CAPTURE_RUNNING) {
377                     if (!mFullScreen) {
378                         // Skip the animation if no longer in full screen mode
379                         drawn = false;
380                     } else {
381                         drawn = mCaptureAnimManager.drawAnimation(canvas, this, mAnimTexture);
382                     }
383                 } else {
384                     drawn = mSwitchAnimManager.drawAnimation(canvas, x, y,
385                             width, height, this, mAnimTexture);
386                 }
387                 if (drawn) {
388                     mListener.requestRender();
389                 } else {
390                     // Continue to the normal draw procedure if the animation is
391                     // not drawn.
392                     mAnimState = ANIM_NONE;
393                     directDraw(canvas, x, y, width, height);
394                 }
395             }
396             callbackIfNeeded();
397         } // mLock
398     }
399 
copyPreviewTexture(GLCanvas canvas)400     private void copyPreviewTexture(GLCanvas canvas) {
401         if (!mDraw.requiresSurfaceTexture() && mAnimTexture == null) {
402             mAnimTexture = new RawTexture(getTextureWidth(), getTextureHeight(), true);
403             mAnimTexture.setIsFlippedVertically(true);
404         }
405         int width = mAnimTexture.getWidth();
406         int height = mAnimTexture.getHeight();
407         canvas.beginRenderTarget(mAnimTexture);
408         if (!mDraw.requiresSurfaceTexture()) {
409             mDraw.onDraw(canvas, 0, 0, width, height);
410         } else {
411             // Flip preview texture vertically. OpenGL uses bottom left point
412             // as the origin (0, 0).
413             canvas.translate(0, height);
414             canvas.scale(1, -1, 1);
415             getSurfaceTexture().getTransformMatrix(mTextureTransformMatrix);
416             updateTransformMatrix(mTextureTransformMatrix);
417             canvas.drawTexture(mExtTexture, mTextureTransformMatrix, 0, 0, width, height);
418         }
419         canvas.endRenderTarget();
420     }
421 
422     @Override
noDraw()423     public void noDraw() {
424         synchronized (mLock) {
425             mVisible = false;
426         }
427     }
428 
429     @Override
recycle()430     public void recycle() {
431         synchronized (mLock) {
432             mVisible = false;
433         }
434     }
435 
436     @Override
onFrameAvailable(SurfaceTexture surfaceTexture)437     public void onFrameAvailable(SurfaceTexture surfaceTexture) {
438         synchronized (mLock) {
439             if (getSurfaceTexture() != surfaceTexture) {
440                 return;
441             }
442             mFirstFrameArrived = true;
443             if (mVisible) {
444                 if (mAnimState == ANIM_SWITCH_WAITING_FIRST_FRAME) {
445                     mAnimState = ANIM_SWITCH_START;
446                 }
447                 // We need to ask for re-render if the SurfaceTexture receives a new
448                 // frame.
449                 mListener.requestRender();
450             }
451         }
452     }
453 
454     // We need to keep track of the size of preview frame on the screen because
455     // it's needed when we do switch-camera animation. See comments in
456     // SwitchAnimManager.java. This is based on the natural orientation, not the
457     // view system orientation.
setPreviewFrameLayoutSize(int width, int height)458     public void setPreviewFrameLayoutSize(int width, int height) {
459         synchronized (mLock) {
460             mSwitchAnimManager.setPreviewFrameLayoutSize(width, height);
461             setPreviewLayoutSize(width, height);
462         }
463     }
464 
setOneTimeOnFrameDrawnListener(OnFrameDrawnListener l)465     public void setOneTimeOnFrameDrawnListener(OnFrameDrawnListener l) {
466         synchronized (mLock) {
467             mFirstFrameArrived = false;
468             mOneTimeFrameDrawnListener = l;
469         }
470     }
471 
472     @Override
getSurfaceTexture()473     public SurfaceTexture getSurfaceTexture() {
474         synchronized (mLock) {
475             SurfaceTexture surfaceTexture = super.getSurfaceTexture();
476             if (surfaceTexture == null && mAcquireTexture) {
477                 try {
478                     mLock.wait();
479                     surfaceTexture = super.getSurfaceTexture();
480                 } catch (InterruptedException e) {
481                     Log.w(TAG, "unexpected interruption");
482                 }
483             }
484             return surfaceTexture;
485         }
486     }
487 
allocateTextureIfRequested(GLCanvas canvas)488     private void allocateTextureIfRequested(GLCanvas canvas) {
489         synchronized (mLock) {
490             if (mAcquireTexture) {
491                 super.acquireSurfaceTexture(canvas);
492                 mAcquireTexture = false;
493                 mLock.notifyAll();
494             }
495         }
496     }
497 }
498