mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-22 18:00:02 +01:00
Update camera permission UI in media.
This commit is contained in:
committed by
Nicholas Tinsley
parent
b14eddefc9
commit
c3c743fbb8
@@ -1,11 +1,11 @@
|
||||
package org.thoughtcrime.securesms.mediasend;
|
||||
|
||||
import android.Manifest;
|
||||
import android.animation.Animator;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
@@ -24,6 +24,8 @@ import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.view.animation.RotateAnimation;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -37,6 +39,7 @@ import androidx.constraintlayout.widget.ConstraintSet;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
import com.google.android.material.card.MaterialCardView;
|
||||
|
||||
import org.signal.core.util.Stopwatch;
|
||||
@@ -57,6 +60,8 @@ import org.thoughtcrime.securesms.mediasend.v2.MediaAnimations;
|
||||
import org.thoughtcrime.securesms.mediasend.v2.MediaCountIndicatorButton;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.util.BottomSheetUtil;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.MemoryFileDescriptor;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
@@ -65,6 +70,7 @@ import org.thoughtcrime.securesms.video.VideoUtil;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
@@ -72,6 +78,8 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import kotlin.Unit;
|
||||
|
||||
import static org.thoughtcrime.securesms.permissions.PermissionDeniedBottomSheet.showPermissionFragment;
|
||||
|
||||
/**
|
||||
* Camera captured implemented using the CameraX SDK, which uses Camera2 under the hood. Should be
|
||||
* preferred whenever possible.
|
||||
@@ -98,6 +106,9 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
|
||||
private CameraXModePolicy cameraXModePolicy;
|
||||
private CameraScreenBrightnessController cameraScreenBrightnessController;
|
||||
private boolean isMediaSelected;
|
||||
private View missingPermissionsContainer;
|
||||
private TextView missingPermissionsText;
|
||||
private MaterialButton allowAccessButton;
|
||||
|
||||
private final Executor qrAnalysisExecutor = Executors.newSingleThreadExecutor();
|
||||
private final QrProcessor qrProcessor = new QrProcessor();
|
||||
@@ -149,13 +160,18 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
|
||||
@SuppressLint("MissingPermission")
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
this.cameraParent = view.findViewById(R.id.camerax_camera_parent);
|
||||
this.cameraParent = view.findViewById(R.id.camerax_camera_parent);
|
||||
|
||||
this.previewView = view.findViewById(R.id.camerax_camera);
|
||||
this.controlsContainer = view.findViewById(R.id.camerax_controls_container);
|
||||
this.cameraXModePolicy = CameraXModePolicy.acquire(requireContext(),
|
||||
controller.getMediaConstraints(),
|
||||
requireArguments().getBoolean(IS_VIDEO_ENABLED, true));
|
||||
this.previewView = view.findViewById(R.id.camerax_camera);
|
||||
this.controlsContainer = view.findViewById(R.id.camerax_controls_container);
|
||||
this.cameraXModePolicy = CameraXModePolicy.acquire(requireContext(),
|
||||
controller.getMediaConstraints(),
|
||||
requireArguments().getBoolean(IS_VIDEO_ENABLED, true));
|
||||
this.missingPermissionsContainer = view.findViewById(R.id.missing_permissions_container);
|
||||
this.missingPermissionsText = view.findViewById(R.id.missing_permissions_text);
|
||||
this.allowAccessButton = view.findViewById(R.id.allow_access_button);
|
||||
|
||||
checkPermissions(requireArguments().getBoolean(IS_VIDEO_ENABLED, true));
|
||||
|
||||
Log.d(TAG, "Starting CameraX with mode policy " + cameraXModePolicy.getClass().getSimpleName());
|
||||
|
||||
@@ -218,6 +234,9 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
|
||||
|
||||
cameraController.bindToLifecycle(getViewLifecycleOwner(), () -> Log.d(TAG, "Camera init complete from onResume"));
|
||||
requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
||||
if (hasCameraPermission()) {
|
||||
missingPermissionsContainer.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -259,6 +278,61 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
private void checkPermissions(boolean includeAudio) {
|
||||
if (hasCameraPermission()) {
|
||||
missingPermissionsContainer.setVisibility(View.GONE);
|
||||
} else {
|
||||
boolean hasAudioPermission = Permissions.hasAll(requireContext(), Manifest.permission.RECORD_AUDIO);
|
||||
missingPermissionsContainer.setVisibility(View.VISIBLE);
|
||||
int textResId = (!includeAudio || hasAudioPermission) ? R.string.CameraXFragment_to_capture_photos_and_video_allow_camera : R.string.CameraXFragment_to_capture_photos_and_video_allow_camera_microphone;
|
||||
missingPermissionsText.setText(textResId);
|
||||
allowAccessButton.setOnClickListener(v -> requestPermissions(includeAudio));
|
||||
}
|
||||
}
|
||||
|
||||
private void requestPermissions(boolean includeAudio) {
|
||||
if (includeAudio) {
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
|
||||
.ifNecessary()
|
||||
.onSomeGranted(permissions -> {
|
||||
if (permissions.contains(Manifest.permission.CAMERA)) {
|
||||
missingPermissionsContainer.setVisibility(View.GONE);
|
||||
}
|
||||
})
|
||||
.onSomePermanentlyDenied(deniedPermissions -> {
|
||||
if (deniedPermissions.containsAll(List.of(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO))) {
|
||||
showPermissionFragment(R.string.CameraXFragment_allow_access_camera_microphone, R.string.CameraXFragment_to_capture_photos_videos).show(getParentFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG);
|
||||
} else if (deniedPermissions.contains(Manifest.permission.CAMERA)) {
|
||||
showPermissionFragment(R.string.CameraXFragment_allow_access_camera, R.string.CameraXFragment_to_capture_photos_videos).show(getParentFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG);
|
||||
}
|
||||
})
|
||||
.onSomeDenied(deniedPermissions -> {
|
||||
if (deniedPermissions.contains(Manifest.permission.CAMERA)) {
|
||||
Toast.makeText(requireContext(), R.string.CameraXFragment_signal_needs_camera_access_capture_photos, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
})
|
||||
.execute();
|
||||
} else {
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.CAMERA)
|
||||
.ifNecessary()
|
||||
.onAllGranted (() -> missingPermissionsContainer.setVisibility(View.GONE))
|
||||
.onAnyDenied(() -> Toast.makeText(requireContext(), R.string.CameraXFragment_signal_needs_camera_access_capture_photos, Toast.LENGTH_LONG).show())
|
||||
.withPermanentDenialDialog(getString(R.string.CameraXFragment_signal_needs_camera_access_capture_photos), null, R.string.CameraXFragment_allow_access_camera, R.string.CameraXFragment_to_capture_photos, getParentFragmentManager())
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasCameraPermission() {
|
||||
return Permissions.hasAll(requireContext(), Manifest.permission.CAMERA);
|
||||
}
|
||||
|
||||
private void onOrientationChanged() {
|
||||
int layout = R.layout.camera_controls_portrait;
|
||||
|
||||
@@ -356,7 +430,7 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
|
||||
selfieFlash = requireView().findViewById(R.id.camera_selfie_flash);
|
||||
|
||||
captureButton.setOnClickListener(v -> {
|
||||
if (cameraController.isInitialized()) {
|
||||
if (hasCameraPermission() && cameraController.isInitialized()) {
|
||||
captureButton.setEnabled(false);
|
||||
flipButton.setEnabled(false);
|
||||
flashButton.setEnabled(false);
|
||||
|
||||
@@ -118,13 +118,17 @@ class CameraXVideoCaptureHelper implements CameraButtonView.VideoCaptureListener
|
||||
public void onVideoCaptureStarted() {
|
||||
Log.d(TAG, "onVideoCaptureStarted");
|
||||
|
||||
if (canRecordAudio()) {
|
||||
if (canUseCamera() && canRecordAudio()) {
|
||||
beginCameraRecording();
|
||||
} else {
|
||||
} else if (!canRecordAudio()) {
|
||||
displayAudioRecordingPermissionsDialog();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canUseCamera() {
|
||||
return Permissions.hasAll(fragment.requireContext(), Manifest.permission.CAMERA);
|
||||
}
|
||||
|
||||
private boolean canRecordAudio() {
|
||||
return Permissions.hasAll(fragment.requireContext(), Manifest.permission.RECORD_AUDIO);
|
||||
}
|
||||
@@ -133,9 +137,9 @@ class CameraXVideoCaptureHelper implements CameraButtonView.VideoCaptureListener
|
||||
Permissions.with(fragment)
|
||||
.request(Manifest.permission.RECORD_AUDIO)
|
||||
.ifNecessary()
|
||||
.withRationaleDialog(fragment.getString(R.string.ConversationActivity_enable_the_microphone_permission_to_capture_videos_with_sound), R.drawable.ic_mic_solid_24)
|
||||
.withPermanentDenialDialog(fragment.getString(R.string.ConversationActivity_signal_needs_the_recording_permissions_to_capture_video))
|
||||
.onAnyDenied(() -> Toast.makeText(fragment.requireContext(), R.string.ConversationActivity_signal_needs_recording_permissions_to_capture_video, Toast.LENGTH_LONG).show())
|
||||
.withRationaleDialog(fragment.getString(R.string.CameraXFragment_allow_access_microphone), fragment.getString(R.string.CameraXFragment_to_capture_videos_with_sound), R.drawable.ic_mic_24)
|
||||
.withPermanentDenialDialog(fragment.getString(R.string.ConversationActivity_signal_needs_the_recording_permissions_to_capture_video), null, R.string.CameraXFragment_allow_access_microphone, R.string.CameraXFragment_to_capture_videos, fragment.getParentFragmentManager())
|
||||
.onAnyDenied(() -> Toast.makeText(fragment.requireContext(), R.string.CameraXFragment_signal_needs_microphone_access_video, Toast.LENGTH_LONG).show())
|
||||
.execute();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.widget.Toast
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.NavController
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.mediasend.camerax.CameraXUtil
|
||||
import org.thoughtcrime.securesms.permissions.PermissionCompat
|
||||
import org.thoughtcrime.securesms.permissions.Permissions
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
@@ -33,14 +34,18 @@ class MediaSelectionNavigator(
|
||||
fun Fragment.requestPermissionsForCamera(
|
||||
onGranted: () -> Unit
|
||||
) {
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.CAMERA)
|
||||
.ifNecessary()
|
||||
.withRationaleDialog(getString(R.string.ConversationActivity_to_capture_photos_and_video_allow_signal_access_to_the_camera), R.drawable.ic_camera_24)
|
||||
.withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_needs_the_camera_permission_to_take_photos_or_video))
|
||||
.onAllGranted(onGranted)
|
||||
.onAnyDenied { Toast.makeText(requireContext(), R.string.ConversationActivity_signal_needs_camera_permissions_to_take_photos_or_video, Toast.LENGTH_LONG).show() }
|
||||
.execute()
|
||||
if (CameraXUtil.isSupported()) {
|
||||
onGranted()
|
||||
} else {
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.CAMERA)
|
||||
.ifNecessary()
|
||||
.withRationaleDialog(getString(R.string.CameraXFragment_allow_access_camera), getString(R.string.CameraXFragment_to_capture_photos_and_video_allow_camera), R.drawable.ic_camera_24)
|
||||
.withPermanentDenialDialog(getString(R.string.CameraXFragment_signal_needs_camera_access_capture_photos), null, R.string.CameraXFragment_allow_access_camera, R.string.CameraXFragment_to_capture_photos_videos, getParentFragmentManager())
|
||||
.onAllGranted(onGranted)
|
||||
.onAnyDenied { Toast.makeText(requireContext(), R.string.CameraXFragment_signal_needs_camera_access_capture_photos, Toast.LENGTH_LONG).show() }
|
||||
.execute()
|
||||
}
|
||||
}
|
||||
|
||||
fun Fragment.requestPermissionsForGallery(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.thoughtcrime.securesms.mediasend.v2.gallery
|
||||
|
||||
import android.Manifest
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
@@ -19,6 +20,7 @@ import org.thoughtcrime.securesms.components.recyclerview.GridDividerDecoration
|
||||
import org.thoughtcrime.securesms.databinding.V2MediaGalleryFragmentBinding
|
||||
import org.thoughtcrime.securesms.mediasend.Media
|
||||
import org.thoughtcrime.securesms.mediasend.MediaRepository
|
||||
import org.thoughtcrime.securesms.mediasend.camerax.CameraXUtil
|
||||
import org.thoughtcrime.securesms.permissions.PermissionCompat
|
||||
import org.thoughtcrime.securesms.permissions.Permissions
|
||||
import org.thoughtcrime.securesms.util.Material3OnScrollHelper
|
||||
@@ -94,7 +96,18 @@ class MediaGalleryFragment : Fragment(R.layout.v2_media_gallery_fragment) {
|
||||
if (callbacks.isCameraEnabled()) {
|
||||
binding.mediaGalleryToolbar.setOnMenuItemClickListener { item ->
|
||||
if (item.itemId == R.id.action_camera) {
|
||||
callbacks.onNavigateToCamera()
|
||||
if (CameraXUtil.isSupported()) {
|
||||
callbacks.onNavigateToCamera()
|
||||
} else {
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.CAMERA)
|
||||
.ifNecessary()
|
||||
.onAllGranted { callbacks.onNavigateToCamera() }
|
||||
.withRationaleDialog(getString(R.string.CameraXFragment_allow_access_camera), getString(R.string.CameraXFragment_to_capture_photos_and_video_allow_camera), R.drawable.ic_camera_24)
|
||||
.withPermanentDenialDialog(getString(R.string.CameraXFragment_signal_needs_camera_access_capture_photos), null, R.string.CameraXFragment_allow_access_camera, R.string.CameraXFragment_to_capture_photos_videos, getParentFragmentManager())
|
||||
.onAnyDenied { Toast.makeText(requireContext(), R.string.CameraXFragment_signal_needs_camera_access_capture_photos, Toast.LENGTH_LONG).show() }
|
||||
.execute()
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
|
||||
Reference in New Issue
Block a user