fix camera ui thread hangs and view race condition

also add a background behind the camera preview surface to avoid
transparency peek-through issues.

Fixes #3576
Closes #3601
// FREEBIE"
This commit is contained in:
Jake McGinty 2015-07-10 10:59:59 -07:00 committed by Moxie Marlinspike
parent 72735baa11
commit 5eaaadad26
6 changed files with 73 additions and 43 deletions

View File

@ -6,19 +6,20 @@
android:id="@+id/layout_container" android:id="@+id/layout_container"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:layout_height="fill_parent"
android:background="?conversation_background"
android:orientation="vertical"> android:orientation="vertical">
<org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer <org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/quick_attachment_drawer" android:id="@+id/quick_attachment_drawer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:background="@color/black">
<RelativeLayout android:layout_width="fill_parent" <RelativeLayout android:layout_width="fill_parent"
android:layout_height="fill_parent" android:layout_height="fill_parent"
android:layout_weight="1" android:layout_weight="1"
android:orientation="vertical" android:orientation="vertical"
android:background="?conversation_background"
android:gravity="bottom"> android:gravity="bottom">
<FrameLayout android:id="@+id/fragment_content" <FrameLayout android:id="@+id/fragment_content"

View File

@ -19,6 +19,7 @@ import android.annotation.TargetApi;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.graphics.Color;
import android.hardware.Camera; import android.hardware.Camera;
import android.hardware.Camera.PreviewCallback; import android.hardware.Camera.PreviewCallback;
import android.os.Build; import android.os.Build;
@ -29,6 +30,7 @@ import android.util.Log;
import android.view.OrientationEventListener; import android.view.OrientationEventListener;
import android.view.Surface; import android.view.Surface;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import java.io.IOException; import java.io.IOException;
@ -36,7 +38,6 @@ import java.util.concurrent.CountDownLatch;
import com.commonsware.cwac.camera.CameraHost; import com.commonsware.cwac.camera.CameraHost;
import com.commonsware.cwac.camera.CameraHost.FailureReason; import com.commonsware.cwac.camera.CameraHost.FailureReason;
import com.commonsware.cwac.camera.PreviewStrategy;
import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
@ -85,11 +86,12 @@ public class CameraView extends FrameLayout {
} else { } else {
previewStrategy = new SurfacePreviewStrategy(this); previewStrategy = new SurfacePreviewStrategy(this);
} }
addView(previewStrategy.getWidget());
} }
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void onResume() { public void onResume() {
addView(previewStrategy.getWidget()); Log.w(TAG, "onResume()");
final CameraHost host = getHost(); final CameraHost host = getHost();
submitTask(new SerializedAsyncTask<FailureReason>() { submitTask(new SerializedAsyncTask<FailureReason>() {
@Override protected FailureReason onRunBackground() { @Override protected FailureReason onRunBackground() {
@ -120,6 +122,8 @@ public class CameraView extends FrameLayout {
synchronized (CameraView.this) { synchronized (CameraView.this) {
CameraView.this.notifyAll(); CameraView.this.notifyAll();
} }
previewCreated();
initPreview();
requestLayout(); requestLayout();
invalidate(); invalidate();
} }
@ -127,7 +131,7 @@ public class CameraView extends FrameLayout {
} }
public void onPause() { public void onPause() {
removeView(previewStrategy.getWidget()); Log.w(TAG, "onPause()");
submitTask(new SerializedAsyncTask<Void>() { submitTask(new SerializedAsyncTask<Void>() {
@Override protected void onPreMain() { @Override protected void onPreMain() {
cameraReady = false; cameraReady = false;
@ -155,7 +159,6 @@ public class CameraView extends FrameLayout {
@Override @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0 && camera != null && cameraReady) { if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0 && camera != null && cameraReady) {
Camera.Size newSize = null; Camera.Size newSize = null;
@ -194,13 +197,14 @@ public class CameraView extends FrameLayout {
} }
} }
} }
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
} }
// based on CameraPreview.java from ApiDemos // based on CameraPreview.java from ApiDemos
@SuppressWarnings("SuspiciousNameCombination") @Override @SuppressWarnings("SuspiciousNameCombination") @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) { protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (changed && getChildCount() > 0) { if (getChildCount() > 0) {
final View child = getChildAt(0); final View child = getChildAt(0);
final int width = r - l; final int width = r - l;
final int height = b - t; final int height = b - t;
@ -309,6 +313,8 @@ public class CameraView extends FrameLayout {
} }
private void stopPreview() { private void stopPreview() {
Log.w(TAG, "stopPreview()");
camera.startPreview();
inPreview = false; inPreview = false;
getHost().autoFocusUnavailable(); getHost().autoFocusUnavailable();
camera.stopPreview(); camera.stopPreview();
@ -491,7 +497,7 @@ public class CameraView extends FrameLayout {
private abstract class PostInitializationTask<Result> extends SerializedAsyncTask<Result> { private abstract class PostInitializationTask<Result> extends SerializedAsyncTask<Result> {
@Override protected void onWait() { @Override protected void onWait() {
synchronized (CameraView.this) { synchronized (CameraView.this) {
while (camera == null || previewSize == null) { while (camera == null || previewSize == null || !previewStrategy.isReady()) {
Util.wait(CameraView.this, 0); Util.wait(CameraView.this, 0);
} }
} }

View File

@ -0,0 +1,12 @@
package org.thoughtcrime.securesms.components.camera;
import android.hardware.Camera;
import android.media.MediaRecorder;
import android.view.View;
import java.io.IOException;
@SuppressWarnings("deprecation")
public interface PreviewStrategy extends com.commonsware.cwac.camera.PreviewStrategy {
boolean isReady();
}

View File

@ -88,7 +88,7 @@ public class QuickAttachmentDrawer extends ViewGroup {
} }
public boolean isOpen() { public boolean isOpen() {
return getDrawerState().isVisible(); return drawerState.isVisible();
} }
public void close() { public void close() {
@ -101,13 +101,13 @@ public class QuickAttachmentDrawer extends ViewGroup {
public void onConfigurationChanged() { public void onConfigurationChanged() {
int rotation = getWindowManager().getDefaultDisplay().getRotation(); int rotation = getWindowManager().getDefaultDisplay().getRotation();
Log.w(TAG, String.format("onNewOrientation(old %d, new %d)", this.rotation, rotation));
final boolean rotationChanged = this.rotation != rotation; final boolean rotationChanged = this.rotation != rotation;
this.rotation = rotation; this.rotation = rotation;
if (rotationChanged) { if (rotationChanged) {
Log.w(TAG, String.format("onNewOrientation(old %d, new %d)", this.rotation, rotation));
if (isOpen()) { if (isOpen()) {
quickCamera.onPause(); quickCamera.onPause();
setDrawerStateAndAnimate(getDrawerState()); setDrawerStateAndAnimate(drawerState);
} }
updateControlsView(); updateControlsView();
} }
@ -140,6 +140,7 @@ public class QuickAttachmentDrawer extends ViewGroup {
} }
private void updateHalfExpandedAnchorPoint() { private void updateHalfExpandedAnchorPoint() {
Log.w(TAG, "updateHalfExpandedAnchorPoint()");
getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() { getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@SuppressWarnings("deprecation") @Override public void onGlobalLayout() { @SuppressWarnings("deprecation") @Override public void onGlobalLayout() {
if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
@ -151,6 +152,9 @@ public class QuickAttachmentDrawer extends ViewGroup {
coverView = getChildAt(coverViewPosition); coverView = getChildAt(coverViewPosition);
slideRange = getMeasuredHeight(); slideRange = getMeasuredHeight();
halfExpandedAnchorPoint = computeSlideOffsetFromCoverBottom(slideRange - baseHalfHeight); halfExpandedAnchorPoint = computeSlideOffsetFromCoverBottom(slideRange - baseHalfHeight);
requestLayout();
invalidate();
Log.w(TAG, "updated halfExpandedAnchorPoint!");
} }
}); });
} }
@ -246,28 +250,34 @@ public class QuickAttachmentDrawer extends ViewGroup {
if (h != oldh) updateHalfExpandedAnchorPoint(); if (h != oldh) updateHalfExpandedAnchorPoint();
} }
@Override // @Override
protected boolean drawChild(@NonNull Canvas canvas, @NonNull View child, long drawingTime) { // protected boolean drawChild(@NonNull Canvas canvas, @NonNull View child, long drawingTime) {
boolean result; // boolean result;
final int save = canvas.save(Canvas.CLIP_SAVE_FLAG); // final int save = canvas.save(Canvas.CLIP_SAVE_FLAG);
//
canvas.getClipBounds(drawChildrenRect); // canvas.getClipBounds(drawChildrenRect);
if (child == coverView) // if (child == coverView)
drawChildrenRect.bottom = Math.min(drawChildrenRect.bottom, child.getBottom()); // drawChildrenRect.bottom = Math.min(drawChildrenRect.bottom, child.getBottom());
else if (coverView != null) // else if (coverView != null)
drawChildrenRect.top = Math.max(drawChildrenRect.top, coverView.getBottom()); // drawChildrenRect.top = Math.max(drawChildrenRect.top, coverView.getBottom());
canvas.clipRect(drawChildrenRect); // canvas.clipRect(drawChildrenRect);
result = super.drawChild(canvas, child, drawingTime); // result = super.drawChild(canvas, child, drawingTime);
canvas.restoreToCount(save); // canvas.restoreToCount(save);
return result; // return result;
} // }
@Override @Override
public void computeScroll() { public void computeScroll() {
if (dragHelper != null && dragHelper.continueSettling(true)) { if (dragHelper != null && dragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this); ViewCompat.postInvalidateOnAnimation(this);
} else if (!getDrawerState().isVisible()) { }
if (slideOffset == COLLAPSED_ANCHOR_POINT && quickCamera.isStarted()) {
Log.w(TAG, "computeScroll(PAUSE)");
quickCamera.onPause(); quickCamera.onPause();
} else if (slideOffset != COLLAPSED_ANCHOR_POINT && !quickCamera.isStarted()) {
Log.w(TAG, "computeScroll(RESUME)");
quickCamera.onResume();
} }
} }
@ -282,12 +292,10 @@ public class QuickAttachmentDrawer extends ViewGroup {
setDrawerState(DrawerState.FULL_EXPANDED); setDrawerState(DrawerState.FULL_EXPANDED);
return; return;
} }
quickCamera.onResume();
fullScreenButton.setImageResource(R.drawable.quick_camera_fullscreen); fullScreenButton.setImageResource(R.drawable.quick_camera_fullscreen);
if (listener != null) listener.onAttachmentDrawerOpened(); if (listener != null) listener.onAttachmentDrawerOpened();
break; break;
case FULL_EXPANDED: case FULL_EXPANDED:
quickCamera.onResume();
fullScreenButton.setImageResource(isFullscreenOnly() ? R.drawable.quick_camera_hide fullScreenButton.setImageResource(isFullscreenOnly() ? R.drawable.quick_camera_hide
: R.drawable.quick_camera_exit_fullscreen); : R.drawable.quick_camera_exit_fullscreen);
if (listener != null) listener.onAttachmentDrawerOpened(); if (listener != null) listener.onAttachmentDrawerOpened();
@ -304,10 +312,6 @@ public class QuickAttachmentDrawer extends ViewGroup {
} }
} }
public DrawerState getDrawerState() {
return drawerState;
}
public void setDrawerStateAndAnimate(final DrawerState requestedDrawerState) { public void setDrawerStateAndAnimate(final DrawerState requestedDrawerState) {
DrawerState oldDrawerState = this.drawerState; DrawerState oldDrawerState = this.drawerState;
setDrawerState(requestedDrawerState); setDrawerState(requestedDrawerState);
@ -487,6 +491,9 @@ public class QuickAttachmentDrawer extends ViewGroup {
dragHelper.smoothSlideViewTo(quickCamera, quickCamera.getLeft(), computeCameraTopPosition(slideOffset)); dragHelper.smoothSlideViewTo(quickCamera, quickCamera.getLeft(), computeCameraTopPosition(slideOffset));
ViewCompat.postInvalidateOnAnimation(this); ViewCompat.postInvalidateOnAnimation(this);
} else { } else {
Log.w(TAG, "quick sliding to " + slideOffset);
this.slideOffset = slideOffset;
requestLayout();
invalidate(); invalidate();
} }
} }

View File

@ -21,8 +21,6 @@ import android.view.SurfaceHolder;
import android.view.SurfaceView; import android.view.SurfaceView;
import android.view.View; import android.view.View;
import com.commonsware.cwac.camera.PreviewStrategy;
import java.io.IOException; import java.io.IOException;
class SurfacePreviewStrategy implements PreviewStrategy, class SurfacePreviewStrategy implements PreviewStrategy,
@ -31,6 +29,7 @@ class SurfacePreviewStrategy implements PreviewStrategy,
private final CameraView cameraView; private final CameraView cameraView;
private SurfaceView preview=null; private SurfaceView preview=null;
private SurfaceHolder previewHolder=null; private SurfaceHolder previewHolder=null;
private boolean ready = false;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
SurfacePreviewStrategy(CameraView cameraView) { SurfacePreviewStrategy(CameraView cameraView) {
@ -44,14 +43,14 @@ class SurfacePreviewStrategy implements PreviewStrategy,
@Override @Override
public void surfaceCreated(SurfaceHolder holder) { public void surfaceCreated(SurfaceHolder holder) {
Log.w(TAG, "surfaceCreated()"); Log.w(TAG, "surfaceCreated()");
cameraView.previewCreated(); ready = true;
synchronized (cameraView) { cameraView.notifyAll(); }
} }
@Override @Override
public void surfaceChanged(SurfaceHolder holder, int format, public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) { int width, int height) {
Log.w(TAG, "surfaceChanged()"); Log.w(TAG, "surfaceChanged()");
cameraView.initPreview();
} }
@Override @Override
@ -75,4 +74,9 @@ class SurfacePreviewStrategy implements PreviewStrategy,
public View getWidget() { public View getWidget() {
return(preview); return(preview);
} }
@Override
public boolean isReady() {
return ready;
}
} }

View File

@ -22,8 +22,6 @@ import android.util.Log;
import android.view.TextureView; import android.view.TextureView;
import android.view.View; import android.view.View;
import com.commonsware.cwac.camera.PreviewStrategy;
import java.io.IOException; import java.io.IOException;
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@ -45,9 +43,7 @@ class TexturePreviewStrategy implements PreviewStrategy,
int width, int height) { int width, int height) {
Log.w(TAG, "onSurfaceTextureAvailable()"); Log.w(TAG, "onSurfaceTextureAvailable()");
this.surface=surface; this.surface=surface;
synchronized (cameraView) { cameraView.notifyAll(); }
cameraView.previewCreated();
cameraView.initPreview();
} }
@Override @Override
@ -72,7 +68,6 @@ class TexturePreviewStrategy implements PreviewStrategy,
@Override @Override
public void attach(Camera camera) throws IOException { public void attach(Camera camera) throws IOException {
Log.w(TAG, "attach(Camera)");
camera.setPreviewTexture(surface); camera.setPreviewTexture(surface);
} }
@ -87,6 +82,11 @@ class TexturePreviewStrategy implements PreviewStrategy,
} }
} }
@Override
public boolean isReady() {
return widget.isAvailable();
}
@Override @Override
public View getWidget() { public View getWidget() {
return(widget); return(widget);