That fuck shit the fascists are using
at master 273 lines 10 kB view raw
1package org.tm.archive.mediasend; 2 3import android.Manifest; 4import android.animation.ValueAnimator; 5import android.annotation.SuppressLint; 6import android.content.Context; 7import android.util.DisplayMetrics; 8import android.util.Size; 9import android.view.ViewGroup; 10import android.view.animation.LinearInterpolator; 11import android.widget.Toast; 12 13import androidx.annotation.NonNull; 14import androidx.annotation.Nullable; 15import androidx.annotation.RequiresApi; 16import androidx.camera.core.ZoomState; 17import androidx.camera.video.FileDescriptorOutputOptions; 18import androidx.camera.video.Recording; 19import androidx.camera.video.VideoRecordEvent; 20import androidx.camera.view.PreviewView; 21import androidx.camera.view.video.AudioConfig; 22import androidx.core.content.ContextCompat; 23import androidx.core.util.Consumer; 24import androidx.fragment.app.Fragment; 25 26import org.signal.core.util.logging.Log; 27import org.tm.archive.R; 28import org.tm.archive.mediasend.camerax.CameraXController; 29import org.tm.archive.mediasend.camerax.CameraXModePolicy; 30import org.tm.archive.permissions.Permissions; 31import org.tm.archive.util.ContextUtil; 32import org.tm.archive.util.Debouncer; 33import org.tm.archive.util.FeatureFlags; 34import org.tm.archive.util.MemoryFileDescriptor; 35import org.tm.archive.video.VideoUtil; 36 37import java.io.FileDescriptor; 38import java.io.IOException; 39import java.util.Objects; 40import java.util.concurrent.TimeUnit; 41 42@RequiresApi(26) 43class CameraXVideoCaptureHelper implements CameraButtonView.VideoCaptureListener { 44 45 private static final String TAG = Log.tag(CameraXVideoCaptureHelper.class); 46 private static final String VIDEO_DEBUG_LABEL = "video-capture"; 47 private static final long VIDEO_SIZE = 10 * 1024 * 1024; 48 49 private final @NonNull Fragment fragment; 50 private final @NonNull PreviewView previewView; 51 private final @NonNull CameraXController cameraController; 52 private final @NonNull Callback callback; 53 private final @NonNull MemoryFileDescriptor memoryFileDescriptor; 54 private final @NonNull ValueAnimator updateProgressAnimator; 55 private final @NonNull Debouncer debouncer; 56 private final @NonNull CameraXModePolicy cameraXModePolicy; 57 58 private ValueAnimator cameraMetricsAnimator; 59 60 private @Nullable Recording activeRecording = null; 61 62 private final Consumer<VideoRecordEvent> videoSavedListener = new Consumer<>() { 63 @Override 64 public void accept(VideoRecordEvent videoRecordEvent) { 65 Log.d(TAG, "Received recording event: " + videoRecordEvent.getClass().getSimpleName()); 66 67 if (videoRecordEvent instanceof VideoRecordEvent.Finalize) { 68 VideoRecordEvent.Finalize event = (VideoRecordEvent.Finalize) videoRecordEvent; 69 70 if (event.hasError()) { 71 Log.w(TAG, "Hit an error while recording! Error code: " + event.getError(), event.getCause()); 72 debouncer.clear(); 73 callback.onVideoError(event.getCause()); 74 } else { 75 try { 76 debouncer.clear(); 77 cameraController.setZoomRatio(getDefaultVideoZoomRatio()); 78 memoryFileDescriptor.seek(0); 79 callback.onVideoSaved(memoryFileDescriptor.getFileDescriptor()); 80 } catch (IOException e) { 81 callback.onVideoError(e); 82 } 83 } 84 } 85 } 86 }; 87 88 CameraXVideoCaptureHelper(@NonNull Fragment fragment, 89 @NonNull CameraButtonView captureButton, 90 @NonNull CameraXController cameraController, 91 @NonNull PreviewView previewView, 92 @NonNull MemoryFileDescriptor memoryFileDescriptor, 93 @NonNull CameraXModePolicy cameraXModePolicy, 94 int maxVideoDurationSec, 95 @NonNull Callback callback) 96 { 97 this.fragment = fragment; 98 this.cameraController = cameraController; 99 this.previewView = previewView; 100 this.memoryFileDescriptor = memoryFileDescriptor; 101 this.callback = callback; 102 103 float animationScale = ContextUtil.getAnimationScale(fragment.requireContext()); 104 long baseDuration = TimeUnit.SECONDS.toMillis(maxVideoDurationSec); 105 long scaledDuration = Math.round(animationScale > 0f ? (baseDuration * (1f / animationScale)) : baseDuration); 106 107 this.updateProgressAnimator = ValueAnimator.ofFloat(0f, 1f).setDuration(scaledDuration); 108 this.debouncer = new Debouncer(TimeUnit.SECONDS.toMillis(maxVideoDurationSec)); 109 this.cameraXModePolicy = cameraXModePolicy; 110 111 updateProgressAnimator.setInterpolator(new LinearInterpolator()); 112 updateProgressAnimator.addUpdateListener(anim -> { 113 captureButton.setProgress(anim.getAnimatedFraction()); 114 }); 115 } 116 117 @Override 118 public void onVideoCaptureStarted() { 119 Log.d(TAG, "onVideoCaptureStarted"); 120 121 if (canRecordAudio()) { 122 beginCameraRecording(); 123 } else { 124 displayAudioRecordingPermissionsDialog(); 125 } 126 } 127 128 private boolean canRecordAudio() { 129 return Permissions.hasAll(fragment.requireContext(), Manifest.permission.RECORD_AUDIO); 130 } 131 132 private void displayAudioRecordingPermissionsDialog() { 133 Permissions.with(fragment) 134 .request(Manifest.permission.RECORD_AUDIO) 135 .ifNecessary() 136 .withRationaleDialog(fragment.getString(R.string.ConversationActivity_enable_the_microphone_permission_to_capture_videos_with_sound), R.drawable.ic_mic_solid_24) 137 .withPermanentDenialDialog(fragment.getString(R.string.ConversationActivity_signal_needs_the_recording_permissions_to_capture_video)) 138 .onAnyDenied(() -> Toast.makeText(fragment.requireContext(), R.string.ConversationActivity_signal_needs_recording_permissions_to_capture_video, Toast.LENGTH_LONG).show()) 139 .execute(); 140 } 141 142 @SuppressLint("RestrictedApi") 143 private void beginCameraRecording() { 144 cameraXModePolicy.setToVideo(cameraController); 145 this.cameraController.setZoomRatio(getDefaultVideoZoomRatio()); 146 callback.onVideoRecordStarted(); 147 shrinkCaptureArea(); 148 149 FileDescriptorOutputOptions outputOptions = new FileDescriptorOutputOptions.Builder(memoryFileDescriptor.getParcelFileDescriptor()).build(); 150 AudioConfig audioConfig = AudioConfig.create(true); 151 152 activeRecording = cameraController.startRecording(outputOptions, audioConfig, ContextCompat.getMainExecutor(fragment.requireContext()), videoSavedListener); 153 154 updateProgressAnimator.start(); 155 debouncer.publish(this::onVideoCaptureComplete); 156 } 157 158 private void shrinkCaptureArea() { 159 Size screenSize = getScreenSize(); 160 Size videoRecordingSize = VideoUtil.getVideoRecordingSize(); 161 float scale = getSurfaceScaleForRecording(); 162 float targetWidthForAnimation = videoRecordingSize.getWidth() * scale; 163 float scaleX = targetWidthForAnimation / screenSize.getWidth(); 164 165 if (scaleX == 1f) { 166 float targetHeightForAnimation = videoRecordingSize.getHeight() * scale; 167 168 if (screenSize.getHeight() == targetHeightForAnimation) { 169 return; 170 } 171 172 cameraMetricsAnimator = ValueAnimator.ofFloat(screenSize.getHeight(), targetHeightForAnimation); 173 } else { 174 175 if (screenSize.getWidth() == targetWidthForAnimation) { 176 return; 177 } 178 179 cameraMetricsAnimator = ValueAnimator.ofFloat(screenSize.getWidth(), targetWidthForAnimation); 180 } 181 182 ViewGroup.LayoutParams params = previewView.getLayoutParams(); 183 cameraMetricsAnimator.setInterpolator(new LinearInterpolator()); 184 cameraMetricsAnimator.setDuration(200); 185 cameraMetricsAnimator.addUpdateListener(animation -> { 186 if (scaleX == 1f) { 187 params.height = Math.round((float) animation.getAnimatedValue()); 188 } else { 189 params.width = Math.round((float) animation.getAnimatedValue()); 190 } 191 previewView.setLayoutParams(params); 192 }); 193 cameraMetricsAnimator.start(); 194 } 195 196 private Size getScreenSize() { 197 DisplayMetrics metrics = previewView.getResources().getDisplayMetrics(); 198 return new Size(metrics.widthPixels, metrics.heightPixels); 199 } 200 201 private float getSurfaceScaleForRecording() { 202 Size videoRecordingSize = VideoUtil.getVideoRecordingSize(); 203 Size screenSize = getScreenSize(); 204 return Math.min(screenSize.getHeight(), screenSize.getWidth()) / (float) Math.min(videoRecordingSize.getHeight(), videoRecordingSize.getWidth()); 205 } 206 207 @Override 208 public void onVideoCaptureComplete() { 209 if (!canRecordAudio()) { 210 Log.w(TAG, "Can't record audio!"); 211 return; 212 } 213 214 if (activeRecording == null) { 215 Log.w(TAG, "No active recording!"); 216 return; 217 } 218 219 Log.d(TAG, "onVideoCaptureComplete"); 220 activeRecording.close(); 221 activeRecording = null; 222 223 if (cameraMetricsAnimator != null && cameraMetricsAnimator.isRunning()) { 224 cameraMetricsAnimator.reverse(); 225 } 226 227 updateProgressAnimator.cancel(); 228 debouncer.clear(); 229 cameraXModePolicy.setToImage(cameraController); 230 } 231 232 @Override 233 public void onZoomIncremented(float increment) { 234 ZoomState zoomState = Objects.requireNonNull(cameraController.getZoomState().getValue()); 235 float range = zoomState.getMaxZoomRatio() - getDefaultVideoZoomRatio(); 236 cameraController.setZoomRatio((range * increment) + getDefaultVideoZoomRatio()); 237 } 238 239 @Override 240 protected void finalize() throws Throwable { 241 if (activeRecording != null) { 242 Log.w(TAG, "Dangling recording left open in finalize()! Attempting to close."); 243 activeRecording.close(); 244 } 245 246 super.finalize(); 247 } 248 249 static MemoryFileDescriptor createFileDescriptor(@NonNull Context context) throws MemoryFileDescriptor.MemoryFileException { 250 return MemoryFileDescriptor.newMemoryFileDescriptor( 251 context, 252 VIDEO_DEBUG_LABEL, 253 VIDEO_SIZE, 254 true//**TM_SA**// 255 ); 256 } 257 258 public float getDefaultVideoZoomRatio() { 259 if (FeatureFlags.startVideoRecordAt1x()) { 260 return 1f; 261 } else { 262 return Objects.requireNonNull(cameraController.getZoomState().getValue()).getMinZoomRatio(); 263 } 264 } 265 266 interface Callback { 267 void onVideoRecordStarted(); 268 269 void onVideoSaved(@NonNull FileDescriptor fd); 270 271 void onVideoError(@Nullable Throwable cause); 272 } 273}