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:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="?conversation_background"
android:orientation="vertical">
<org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/quick_attachment_drawer"
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"
android:layout_height="fill_parent"
android:layout_weight="1"
android:orientation="vertical"
android:background="?conversation_background"
android:gravity="bottom">
<FrameLayout android:id="@+id/fragment_content"

View File

@ -19,6 +19,7 @@ import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.Color;
import android.hardware.Camera;
import android.hardware.Camera.PreviewCallback;
import android.os.Build;
@ -29,6 +30,7 @@ import android.util.Log;
import android.view.OrientationEventListener;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
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.FailureReason;
import com.commonsware.cwac.camera.PreviewStrategy;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.util.Util;
@ -85,11 +86,12 @@ public class CameraView extends FrameLayout {
} else {
previewStrategy = new SurfacePreviewStrategy(this);
}
addView(previewStrategy.getWidget());
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void onResume() {
addView(previewStrategy.getWidget());
Log.w(TAG, "onResume()");
final CameraHost host = getHost();
submitTask(new SerializedAsyncTask<FailureReason>() {
@Override protected FailureReason onRunBackground() {
@ -120,6 +122,8 @@ public class CameraView extends FrameLayout {
synchronized (CameraView.this) {
CameraView.this.notifyAll();
}
previewCreated();
initPreview();
requestLayout();
invalidate();
}
@ -127,7 +131,7 @@ public class CameraView extends FrameLayout {
}
public void onPause() {
removeView(previewStrategy.getWidget());
Log.w(TAG, "onPause()");
submitTask(new SerializedAsyncTask<Void>() {
@Override protected void onPreMain() {
cameraReady = false;
@ -155,7 +159,6 @@ public class CameraView extends FrameLayout {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0 && camera != null && cameraReady) {
Camera.Size newSize = null;
@ -194,13 +197,14 @@ public class CameraView extends FrameLayout {
}
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
// based on CameraPreview.java from ApiDemos
@SuppressWarnings("SuspiciousNameCombination") @Override
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 int width = r - l;
final int height = b - t;
@ -309,6 +313,8 @@ public class CameraView extends FrameLayout {
}
private void stopPreview() {
Log.w(TAG, "stopPreview()");
camera.startPreview();
inPreview = false;
getHost().autoFocusUnavailable();
camera.stopPreview();
@ -491,7 +497,7 @@ public class CameraView extends FrameLayout {
private abstract class PostInitializationTask<Result> extends SerializedAsyncTask<Result> {
@Override protected void onWait() {
synchronized (CameraView.this) {
while (camera == null || previewSize == null) {
while (camera == null || previewSize == null || !previewStrategy.isReady()) {
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() {
return getDrawerState().isVisible();
return drawerState.isVisible();
}
public void close() {
@ -101,13 +101,13 @@ public class QuickAttachmentDrawer extends ViewGroup {
public void onConfigurationChanged() {
int rotation = getWindowManager().getDefaultDisplay().getRotation();
Log.w(TAG, String.format("onNewOrientation(old %d, new %d)", this.rotation, rotation));
final boolean rotationChanged = this.rotation != rotation;
this.rotation = rotation;
if (rotationChanged) {
Log.w(TAG, String.format("onNewOrientation(old %d, new %d)", this.rotation, rotation));
if (isOpen()) {
quickCamera.onPause();
setDrawerStateAndAnimate(getDrawerState());
setDrawerStateAndAnimate(drawerState);
}
updateControlsView();
}
@ -140,6 +140,7 @@ public class QuickAttachmentDrawer extends ViewGroup {
}
private void updateHalfExpandedAnchorPoint() {
Log.w(TAG, "updateHalfExpandedAnchorPoint()");
getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@SuppressWarnings("deprecation") @Override public void onGlobalLayout() {
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);
slideRange = getMeasuredHeight();
halfExpandedAnchorPoint = computeSlideOffsetFromCoverBottom(slideRange - baseHalfHeight);
requestLayout();
invalidate();
Log.w(TAG, "updated halfExpandedAnchorPoint!");
}
});
}
@ -246,28 +250,34 @@ public class QuickAttachmentDrawer extends ViewGroup {
if (h != oldh) updateHalfExpandedAnchorPoint();
}
@Override
protected boolean drawChild(@NonNull Canvas canvas, @NonNull View child, long drawingTime) {
boolean result;
final int save = canvas.save(Canvas.CLIP_SAVE_FLAG);
canvas.getClipBounds(drawChildrenRect);
if (child == coverView)
drawChildrenRect.bottom = Math.min(drawChildrenRect.bottom, child.getBottom());
else if (coverView != null)
drawChildrenRect.top = Math.max(drawChildrenRect.top, coverView.getBottom());
canvas.clipRect(drawChildrenRect);
result = super.drawChild(canvas, child, drawingTime);
canvas.restoreToCount(save);
return result;
}
// @Override
// protected boolean drawChild(@NonNull Canvas canvas, @NonNull View child, long drawingTime) {
// boolean result;
// final int save = canvas.save(Canvas.CLIP_SAVE_FLAG);
//
// canvas.getClipBounds(drawChildrenRect);
// if (child == coverView)
// drawChildrenRect.bottom = Math.min(drawChildrenRect.bottom, child.getBottom());
// else if (coverView != null)
// drawChildrenRect.top = Math.max(drawChildrenRect.top, coverView.getBottom());
// canvas.clipRect(drawChildrenRect);
// result = super.drawChild(canvas, child, drawingTime);
// canvas.restoreToCount(save);
// return result;
// }
@Override
public void computeScroll() {
if (dragHelper != null && dragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
} else if (!getDrawerState().isVisible()) {
}
if (slideOffset == COLLAPSED_ANCHOR_POINT && quickCamera.isStarted()) {
Log.w(TAG, "computeScroll(PAUSE)");
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);
return;
}
quickCamera.onResume();
fullScreenButton.setImageResource(R.drawable.quick_camera_fullscreen);
if (listener != null) listener.onAttachmentDrawerOpened();
break;
case FULL_EXPANDED:
quickCamera.onResume();
fullScreenButton.setImageResource(isFullscreenOnly() ? R.drawable.quick_camera_hide
: R.drawable.quick_camera_exit_fullscreen);
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) {
DrawerState oldDrawerState = this.drawerState;
setDrawerState(requestedDrawerState);
@ -487,6 +491,9 @@ public class QuickAttachmentDrawer extends ViewGroup {
dragHelper.smoothSlideViewTo(quickCamera, quickCamera.getLeft(), computeCameraTopPosition(slideOffset));
ViewCompat.postInvalidateOnAnimation(this);
} else {
Log.w(TAG, "quick sliding to " + slideOffset);
this.slideOffset = slideOffset;
requestLayout();
invalidate();
}
}

View File

@ -21,8 +21,6 @@ import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import com.commonsware.cwac.camera.PreviewStrategy;
import java.io.IOException;
class SurfacePreviewStrategy implements PreviewStrategy,
@ -31,6 +29,7 @@ class SurfacePreviewStrategy implements PreviewStrategy,
private final CameraView cameraView;
private SurfaceView preview=null;
private SurfaceHolder previewHolder=null;
private boolean ready = false;
@SuppressWarnings("deprecation")
SurfacePreviewStrategy(CameraView cameraView) {
@ -44,14 +43,14 @@ class SurfacePreviewStrategy implements PreviewStrategy,
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.w(TAG, "surfaceCreated()");
cameraView.previewCreated();
ready = true;
synchronized (cameraView) { cameraView.notifyAll(); }
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {
Log.w(TAG, "surfaceChanged()");
cameraView.initPreview();
}
@Override
@ -75,4 +74,9 @@ class SurfacePreviewStrategy implements PreviewStrategy,
public View getWidget() {
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.View;
import com.commonsware.cwac.camera.PreviewStrategy;
import java.io.IOException;
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@ -45,9 +43,7 @@ class TexturePreviewStrategy implements PreviewStrategy,
int width, int height) {
Log.w(TAG, "onSurfaceTextureAvailable()");
this.surface=surface;
cameraView.previewCreated();
cameraView.initPreview();
synchronized (cameraView) { cameraView.notifyAll(); }
}
@Override
@ -72,7 +68,6 @@ class TexturePreviewStrategy implements PreviewStrategy,
@Override
public void attach(Camera camera) throws IOException {
Log.w(TAG, "attach(Camera)");
camera.setPreviewTexture(surface);
}
@ -87,6 +82,11 @@ class TexturePreviewStrategy implements PreviewStrategy,
}
}
@Override
public boolean isReady() {
return widget.isAvailable();
}
@Override
public View getWidget() {
return(widget);