Add CameraXFlashToggleView and selfie flash.

master
alex-signal 2019-09-06 13:28:54 -03:00 committed by Greyson Parrelli
parent 70347e754c
commit f81c0b448e
24 changed files with 298 additions and 4 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 926 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 776 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 588 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<item app:state_flash_auto="true" android:drawable="@drawable/flash_auto_32" />
<item app:state_flash_off="true" android:drawable="@drawable/flash_off_32" />
<item app:state_flash_on="true" android:drawable="@drawable/flash_on_32" />
</selector>

View File

@ -17,6 +17,16 @@
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<org.thoughtcrime.securesms.mediasend.camerax.CameraXFlashToggleView
android:id="@+id/camera_flash_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="14dp"
android:src="@drawable/camerax_flash_toggle"
app:layout_constraintStart_toEndOf="@+id/camera_flip_button"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/camera_flip_button"
android:layout_width="wrap_content"
@ -60,5 +70,16 @@
app:layout_constraintEnd_toEndOf="@id/camera_capture_button"
tools:visibility="visible" />
<View
android:id="@+id/camera_selfie_flash"
android:layout_width="0dp"
android:layout_height="0dp"
android:alpha="0"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:background="@color/white" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -17,6 +17,16 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<org.thoughtcrime.securesms.mediasend.camerax.CameraXFlashToggleView
android:id="@+id/camera_flash_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="14dp"
android:layout_marginEnd="16dp"
android:src="@drawable/camerax_flash_toggle"
app:layout_constraintEnd_toStartOf="@+id/camera_flip_button"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/camera_flip_button"
android:layout_width="wrap_content"
@ -61,5 +71,16 @@
app:layout_constraintEnd_toEndOf="parent"
tools:visibility="visible" />
<View
android:id="@+id/camera_selfie_flash"
android:layout_width="0dp"
android:layout_height="0dp"
android:alpha="0"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:background="@color/white" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -362,4 +362,10 @@
<enum name="top" value="1" />
</attr>
</declare-styleable>
<declare-styleable name="CameraXFlashState">
<attr name="state_flash_auto" format="boolean" />
<attr name="state_flash_off" format="boolean" />
<attr name="state_flash_on" format="boolean" />
</declare-styleable>
</resources>

View File

@ -29,6 +29,7 @@ import com.bumptech.glide.Glide;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mediasend.camerax.CameraXFlashToggleView;
import org.thoughtcrime.securesms.mediasend.camerax.CameraXUtil;
import org.thoughtcrime.securesms.mediasend.camerax.CameraXView;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
@ -52,6 +53,7 @@ public class CameraXFragment extends Fragment implements CameraFragment {
private ViewGroup controlsContainer;
private Controller controller;
private MediaSendViewModel viewModel;
private View selfieFlash;
public static CameraXFragment newInstance() {
return new CameraXFragment();
@ -160,14 +162,18 @@ public class CameraXFragment extends Fragment implements CameraFragment {
@SuppressLint({"ClickableViewAccessibility", "MissingPermission"})
private void initControls() {
View flipButton = requireView().findViewById(R.id.camera_flip_button);
View captureButton = requireView().findViewById(R.id.camera_capture_button);
View galleryButton = requireView().findViewById(R.id.camera_gallery_button);
View countButton = requireView().findViewById(R.id.camera_count_button);
View flipButton = requireView().findViewById(R.id.camera_flip_button);
View captureButton = requireView().findViewById(R.id.camera_capture_button);
View galleryButton = requireView().findViewById(R.id.camera_gallery_button);
View countButton = requireView().findViewById(R.id.camera_count_button);
CameraXFlashToggleView flashButton = requireView().findViewById(R.id.camera_flash_button);
selfieFlash = requireView().findViewById(R.id.camera_selfie_flash);
captureButton.setOnClickListener(v -> {
captureButton.setEnabled(false);
flipButton.setEnabled(false);
flashButton.setEnabled(false);
onCaptureClicked();
});
@ -181,6 +187,8 @@ public class CameraXFragment extends Fragment implements CameraFragment {
animation.setDuration(200);
animation.setInterpolator(new DecelerateInterpolator());
flipButton.startAnimation(animation);
flashButton.setAutoFlashEnabled(camera.hasFlash());
flashButton.setFlash(camera.getFlash());
});
GestureDetector gestureDetector = new GestureDetector(requireContext(), new GestureDetector.SimpleOnGestureListener() {
@ -199,6 +207,10 @@ public class CameraXFragment extends Fragment implements CameraFragment {
flipButton.setVisibility(View.GONE);
}
flashButton.setAutoFlashEnabled(camera.hasFlash());
flashButton.setFlash(camera.getFlash());
flashButton.setOnFlashModeChangedListener(camera::setFlash);
galleryButton.setOnClickListener(v -> controller.onGalleryClicked());
countButton.setOnClickListener(v -> controller.onCameraCountButtonClicked());
@ -208,9 +220,17 @@ public class CameraXFragment extends Fragment implements CameraFragment {
private void onCaptureClicked() {
Stopwatch stopwatch = new Stopwatch("Capture");
CameraXSelfieFlashHelper flashHelper = new CameraXSelfieFlashHelper(
requireActivity().getWindow(),
camera,
selfieFlash
);
camera.takePicture(new ImageCapture.OnImageCapturedListener() {
@Override
public void onCaptureSuccess(ImageProxy image, int rotationDegrees) {
flashHelper.endFlash();
SimpleTask.run(CameraXFragment.this.getLifecycle(), () -> {
stopwatch.split("captured");
try {
@ -234,8 +254,11 @@ public class CameraXFragment extends Fragment implements CameraFragment {
@Override
public void onError(ImageCapture.UseCaseError useCaseError, String message, @Nullable Throwable cause) {
flashHelper.endFlash();
controller.onCameraError();
}
});
flashHelper.startFlash();
}
}

View File

@ -0,0 +1,72 @@
package org.thoughtcrime.securesms.mediasend;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.camera.core.CameraX;
import androidx.camera.core.FlashMode;
import org.thoughtcrime.securesms.mediasend.camerax.CameraXView;
@RequiresApi(21)
final class CameraXSelfieFlashHelper {
private static final float MAX_SCREEN_BRIGHTNESS = 1f;
private static final float MAX_SELFIE_FLASH_ALPHA = 0.75f;
private static final long SELFIE_FLASH_DURATION_MS = 250;
private final Window window;
private final CameraXView camera;
private final View selfieFlash;
private float brightnessBeforeFlash;
private boolean inFlash;
CameraXSelfieFlashHelper(@NonNull Window window,
@NonNull CameraXView camera,
@NonNull View selfieFlash)
{
this.window = window;
this.camera = camera;
this.selfieFlash = selfieFlash;
}
void startFlash() {
if (inFlash || !shouldUseViewBasedFlash()) return;
inFlash = true;
WindowManager.LayoutParams params = window.getAttributes();
brightnessBeforeFlash = params.screenBrightness;
params.screenBrightness = MAX_SCREEN_BRIGHTNESS;
window.setAttributes(params);
selfieFlash.animate()
.alpha(MAX_SELFIE_FLASH_ALPHA)
.setDuration(SELFIE_FLASH_DURATION_MS);
}
void endFlash() {
if (!inFlash) return;
WindowManager.LayoutParams params = window.getAttributes();
params.screenBrightness = brightnessBeforeFlash;
window.setAttributes(params);
selfieFlash.animate()
.alpha(0f)
.setDuration(SELFIE_FLASH_DURATION_MS);
inFlash = false;
}
private boolean shouldUseViewBasedFlash() {
return camera.getFlash() == FlashMode.ON &&
!camera.hasFlash() &&
camera.getCameraLensFacing() == CameraX.LensFacing.FRONT;
}
}

View File

@ -0,0 +1,131 @@
package org.thoughtcrime.securesms.mediasend.camerax;
import android.content.Context;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.camera.core.FlashMode;
import org.thoughtcrime.securesms.R;
import java.util.Arrays;
import java.util.List;
public final class CameraXFlashToggleView extends AppCompatImageView {
private static final String STATE_FLASH_INDEX = "flash.toggle.state.flash.index";
private static final String STATE_SUPPORT_AUTO = "flash.toggle.state.support.auto";
private static final String STATE_PARENT = "flash.toggle.state.parent";
private static final int[] FLASH_AUTO = { R.attr.state_flash_auto };
private static final int[] FLASH_OFF = { R.attr.state_flash_off };
private static final int[] FLASH_ON = { R.attr.state_flash_on };
private static final int[][] FLASH_ENUM = { FLASH_AUTO, FLASH_OFF, FLASH_ON };
private static final List<FlashMode> FLASH_MODES = Arrays.asList(FlashMode.AUTO, FlashMode.OFF, FlashMode.ON);
private static final FlashMode FLASH_FALLBACK = FlashMode.OFF;
private boolean supportsFlashModeAuto = true;
private int flashIndex;
private OnFlashModeChangedListener flashModeChangedListener;
public CameraXFlashToggleView(Context context) {
this(context, null);
}
public CameraXFlashToggleView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CameraXFlashToggleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
super.setOnClickListener((v) -> setFlash(FLASH_MODES.get((flashIndex + 1) % FLASH_ENUM.length)));
}
@Override
public int[] onCreateDrawableState(int extraSpace) {
final int[] extra = FLASH_ENUM[flashIndex];
final int[] drawableState = super.onCreateDrawableState(extraSpace + extra.length);
mergeDrawableStates(drawableState, extra);
return drawableState;
}
@Override
public void setOnClickListener(@Nullable OnClickListener l) {
throw new IllegalStateException("This View does not support custom click listeners.");
}
public void setAutoFlashEnabled(boolean isAutoEnabled) {
supportsFlashModeAuto = isAutoEnabled;
setFlash(FLASH_MODES.get(flashIndex));
}
public void setFlash(@NonNull FlashMode flashMode) {
flashIndex = resolveFlashIndex(FLASH_MODES.indexOf(flashMode), supportsFlashModeAuto);
refreshDrawableState();
notifyListener();
}
public void setOnFlashModeChangedListener(@Nullable OnFlashModeChangedListener listener) {
this.flashModeChangedListener = listener;
}
@Override
protected Parcelable onSaveInstanceState() {
Parcelable parentState = super.onSaveInstanceState();
Bundle bundle = new Bundle();
bundle.putParcelable(STATE_PARENT, parentState);
bundle.putInt(STATE_FLASH_INDEX, flashIndex);
bundle.putBoolean(STATE_SUPPORT_AUTO, supportsFlashModeAuto);
return bundle;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle savedState = (Bundle) state;
supportsFlashModeAuto = savedState.getBoolean(STATE_SUPPORT_AUTO);
setFlash(FLASH_MODES.get(
resolveFlashIndex(savedState.getInt(STATE_FLASH_INDEX), supportsFlashModeAuto))
);
super.onRestoreInstanceState(savedState.getParcelable(STATE_PARENT));
} else {
super.onRestoreInstanceState(state);
}
}
private void notifyListener() {
if (flashModeChangedListener == null) return;
flashModeChangedListener.flashModeChanged(FLASH_MODES.get(flashIndex));
}
private static int resolveFlashIndex(int desiredFlashIndex, boolean supportsFlashModeAuto) {
if (isIllegalFlashIndex(desiredFlashIndex)) {
throw new IllegalArgumentException("Unsupported index: " + desiredFlashIndex);
}
if (isUnsupportedFlashMode(desiredFlashIndex, supportsFlashModeAuto)) {
return FLASH_MODES.indexOf(FLASH_FALLBACK);
}
return desiredFlashIndex;
}
private static boolean isIllegalFlashIndex(int desiredFlashIndex) {
return desiredFlashIndex < 0 || desiredFlashIndex > FLASH_ENUM.length;
}
private static boolean isUnsupportedFlashMode(int desiredFlashIndex, boolean supportsFlashModeAuto) {
return FLASH_MODES.get(desiredFlashIndex) == FlashMode.AUTO && !supportsFlashModeAuto;
}
public interface OnFlashModeChangedListener {
void flashModeChanged(FlashMode flashMode);
}
}

View File

@ -686,6 +686,16 @@ final class CameraXModule {
mImageCapture.setFlashMode(flash);
}
public boolean hasFlash() {
try {
Boolean flashInfoAvailable = mCameraManager.getCameraCharacteristics(getActiveCamera())
.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
return flashInfoAvailable == Boolean.TRUE;
} catch (CameraInfoUnavailableException | CameraAccessException e) {
return false;
}
}
public void enableTorch(boolean torch) {
if (mPreview == null) {
return;

View File

@ -683,6 +683,10 @@ public final class CameraXView extends ViewGroup {
mCameraModule.setFlash(flashMode);
}
public boolean hasFlash() {
return mCameraModule.hasFlash();
}
private int getRelativeCameraOrientation(boolean compensateForMirroring) {
return mCameraModule.getRelativeCameraOrientation(compensateForMirroring);
}