Add CameraXFlashToggleView and selfie flash.
Po Szerokość: | Wysokość: | Rozmiar: 1.4 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 1.2 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 920 B |
Po Szerokość: | Wysokość: | Rozmiar: 926 B |
Po Szerokość: | Wysokość: | Rozmiar: 776 B |
Po Szerokość: | Wysokość: | Rozmiar: 588 B |
Po Szerokość: | Wysokość: | Rozmiar: 2.0 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 1.6 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 1.2 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 3.4 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 2.7 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 1.8 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 4.2 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 3.3 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 2.1 KiB |
|
@ -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>
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|