That fuck shit the fascists are using
at master 504 lines 20 kB view raw
1package org.tm.archive.mediasend; 2 3import android.animation.Animator; 4import android.annotation.SuppressLint; 5import android.content.pm.ActivityInfo; 6import android.content.res.Configuration; 7import android.graphics.Bitmap; 8import android.graphics.Color; 9import android.graphics.Matrix; 10import android.graphics.Point; 11import android.graphics.PointF; 12import android.graphics.SurfaceTexture; 13import android.graphics.drawable.Drawable; 14import android.hardware.Camera; 15import android.os.Bundle; 16import android.view.Display; 17import android.view.GestureDetector; 18import android.view.LayoutInflater; 19import android.view.MotionEvent; 20import android.view.TextureView; 21import android.view.View; 22import android.view.ViewGroup; 23import android.view.WindowManager; 24import android.view.animation.Animation; 25import android.view.animation.DecelerateInterpolator; 26import android.view.animation.RotateAnimation; 27import android.widget.ImageButton; 28import android.widget.ImageView; 29 30import androidx.annotation.NonNull; 31import androidx.annotation.Nullable; 32import androidx.constraintlayout.widget.ConstraintLayout; 33import androidx.constraintlayout.widget.ConstraintSet; 34 35import com.bumptech.glide.Glide; 36import com.bumptech.glide.load.MultiTransformation; 37import com.bumptech.glide.load.Transformation; 38import com.bumptech.glide.load.resource.bitmap.CenterCrop; 39import com.bumptech.glide.request.target.SimpleTarget; 40import com.bumptech.glide.request.transition.Transition; 41import com.google.android.material.card.MaterialCardView; 42 43import org.signal.core.util.Stopwatch; 44import org.signal.core.util.logging.Log; 45import org.tm.archive.LoggingFragment; 46import org.tm.archive.R; 47import org.tm.archive.animation.AnimationCompleteListener; 48import org.tm.archive.mediasend.camerax.CameraXModelBlocklist; 49import org.tm.archive.mediasend.v2.MediaAnimations; 50import org.tm.archive.mediasend.v2.MediaCountIndicatorButton; 51import org.tm.archive.mms.DecryptableStreamUriLoader.DecryptableUri; 52import org.tm.archive.util.ServiceUtil; 53import org.tm.archive.util.TextSecurePreferences; 54import org.tm.archive.util.ViewUtil; 55 56import java.io.ByteArrayOutputStream; 57 58import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; 59import io.reactivex.rxjava3.disposables.Disposable; 60 61/** 62 * Camera capture implemented with the legacy camera API's. Should only be used if a device is on the {@link CameraXModelBlocklist}. 63 */ 64public class Camera1Fragment extends LoggingFragment implements CameraFragment, 65 TextureView.SurfaceTextureListener, 66 Camera1Controller.EventListener 67{ 68 69 private static final String TAG = Log.tag(Camera1Fragment.class); 70 71 private TextureView cameraPreview; 72 private ViewGroup controlsContainer; 73 private ImageButton flipButton; 74 private View captureButton; 75 private Camera1Controller camera; 76 private Controller controller; 77 private OrderEnforcer<Stage> orderEnforcer; 78 private Camera1Controller.Properties properties; 79 private RotationListener rotationListener; 80 private Disposable rotationListenerDisposable; 81 private Disposable mostRecentItemDisposable = Disposable.disposed(); 82 private CameraScreenBrightnessController cameraScreenBrightnessController; 83 private boolean isMediaSelected; 84 85 public static Camera1Fragment newInstance() { 86 return new Camera1Fragment(); 87 } 88 89 @Override 90 public void onCreate(@Nullable Bundle savedInstanceState) { 91 super.onCreate(savedInstanceState); 92 93 if (getActivity() instanceof Controller) { 94 this.controller = (Controller) getActivity(); 95 } else if (getParentFragment() instanceof Controller) { 96 this.controller = (Controller) getParentFragment(); 97 } 98 99 if (controller == null) { 100 throw new IllegalStateException("Parent must implement controller interface."); 101 } 102 103 WindowManager windowManager = ServiceUtil.getWindowManager(getActivity()); 104 Display display = windowManager.getDefaultDisplay(); 105 Point displaySize = new Point(); 106 107 display.getSize(displaySize); 108 109 camera = new Camera1Controller(TextSecurePreferences.getDirectCaptureCameraId(getContext()), displaySize.x, displaySize.y, this); 110 orderEnforcer = new OrderEnforcer<>(Stage.SURFACE_AVAILABLE, Stage.CAMERA_PROPERTIES_AVAILABLE); 111 } 112 113 @Nullable 114 @Override 115 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 116 return inflater.inflate(R.layout.camera_fragment, container, false); 117 } 118 119 @SuppressLint("ClickableViewAccessibility") 120 @Override 121 public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 122 super.onViewCreated(view, savedInstanceState); 123 requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR); 124 cameraScreenBrightnessController = new CameraScreenBrightnessController(requireActivity().getWindow(), new CameraStateProvider(camera)); 125 getViewLifecycleOwner().getLifecycle().addObserver(cameraScreenBrightnessController); 126 127 rotationListener = new RotationListener(requireContext()); 128 cameraPreview = view.findViewById(R.id.camera_preview); 129 controlsContainer = view.findViewById(R.id.camera_controls_container); 130 131 View cameraParent = view.findViewById(R.id.camera_preview_parent); 132 133 onOrientationChanged(); 134 135 cameraPreview.setSurfaceTextureListener(this); 136 137 GestureDetector gestureDetector = new GestureDetector(flipGestureListener); 138 cameraPreview.setOnTouchListener((v, event) -> gestureDetector.onTouchEvent(event)); 139 140 view.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { 141 // Let's assume portrait for now, so 9:16 142 float aspectRatio = CameraFragment.getAspectRatioForOrientation(Configuration.ORIENTATION_PORTRAIT); 143 float width = right - left; 144 float height = Math.min((1f / aspectRatio) * width, bottom - top); 145 146 ViewGroup.LayoutParams params = cameraParent.getLayoutParams(); 147 148 // If there's a mismatch... 149 if (params.height != (int) height) { 150 params.width = (int) width; 151 params.height = (int) height; 152 153 cameraParent.setLayoutParams(params); 154 } 155 }); 156 } 157 158 @Override 159 public void onResume() { 160 super.onResume(); 161 camera.initialize(); 162 163 if (cameraPreview.isAvailable()) { 164 orderEnforcer.markCompleted(Stage.SURFACE_AVAILABLE); 165 } 166 167 if (properties != null) { 168 orderEnforcer.markCompleted(Stage.CAMERA_PROPERTIES_AVAILABLE); 169 } 170 171 orderEnforcer.run(Stage.SURFACE_AVAILABLE, () -> { 172 camera.linkSurface(cameraPreview.getSurfaceTexture()); 173 }); 174 175 rotationListenerDisposable = rotationListener.getObservable() 176 .distinctUntilChanged() 177 .filter(rotation -> rotation != RotationListener.Rotation.ROTATION_180) 178 .subscribe(rotation -> { 179 orderEnforcer.run(Stage.SURFACE_AVAILABLE, () -> { 180 camera.setScreenRotation(rotation.getSurfaceRotation()); 181 }); 182 }); 183 184 orderEnforcer.run(Stage.CAMERA_PROPERTIES_AVAILABLE, this::updatePreviewScale); 185 requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR); 186 } 187 188 @Override 189 public void onPause() { 190 super.onPause(); 191 rotationListenerDisposable.dispose(); 192 camera.release(); 193 orderEnforcer.reset(); 194 } 195 196 @Override 197 public void onDestroyView() { 198 super.onDestroyView(); 199 mostRecentItemDisposable.dispose(); 200 } 201 202 @Override 203 public void onDestroy() { 204 super.onDestroy(); 205 requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); 206 } 207 208 @Override 209 public void fadeOutControls(@NonNull Runnable onEndAction) { 210 controlsContainer.setEnabled(false); 211 controlsContainer.animate() 212 .setInterpolator(MediaAnimations.getInterpolator()) 213 .setDuration(250) 214 .alpha(0f) 215 .setListener(new AnimationCompleteListener() { 216 @Override 217 public void onAnimationEnd(Animator animation) { 218 controlsContainer.setEnabled(true); 219 onEndAction.run(); 220 } 221 }); 222 } 223 224 @Override 225 public void fadeInControls() { 226 controlsContainer.setEnabled(false); 227 controlsContainer.animate() 228 .setInterpolator(MediaAnimations.getInterpolator()) 229 .setDuration(250) 230 .alpha(1f) 231 .setListener(new AnimationCompleteListener() { 232 @Override 233 public void onAnimationEnd(Animator animation) { 234 controlsContainer.setEnabled(true); 235 } 236 }); 237 } 238 239 @Override 240 public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 241 Log.d(TAG, "onSurfaceTextureAvailable"); 242 orderEnforcer.markCompleted(Stage.SURFACE_AVAILABLE); 243 } 244 245 @Override 246 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 247 orderEnforcer.run(Stage.CAMERA_PROPERTIES_AVAILABLE, this::updatePreviewScale); 248 } 249 250 @Override 251 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 252 return false; 253 } 254 255 @Override 256 public void onSurfaceTextureUpdated(SurfaceTexture surface) { 257 } 258 259 @Override 260 public void onPropertiesAvailable(@NonNull Camera1Controller.Properties properties) { 261 Log.d(TAG, "Got camera properties: " + properties); 262 this.properties = properties; 263 orderEnforcer.run(Stage.CAMERA_PROPERTIES_AVAILABLE, this::updatePreviewScale); 264 orderEnforcer.markCompleted(Stage.CAMERA_PROPERTIES_AVAILABLE); 265 } 266 267 @Override 268 public void onCameraUnavailable() { 269 controller.onCameraError(); 270 } 271 272 private void presentRecentItemThumbnail(@Nullable Media media) { 273 View thumbBackground = controlsContainer.findViewById(R.id.camera_gallery_button_background); 274 ImageView thumbnail = controlsContainer.findViewById(R.id.camera_gallery_button); 275 276 if (media != null) { 277 thumbBackground.setBackgroundResource(R.drawable.circle_tintable); 278 thumbnail.clearColorFilter(); 279 thumbnail.setScaleType(ImageView.ScaleType.FIT_CENTER); 280 Glide.with(this) 281 .load(new DecryptableUri(media.getUri())) 282 .centerCrop() 283 .into(thumbnail); 284 } else { 285 thumbBackground.setBackgroundResource(R.drawable.media_selection_camera_switch_background); 286 thumbnail.setImageResource(R.drawable.symbol_album_tilt_24); 287 thumbnail.setColorFilter(Color.WHITE); 288 thumbnail.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 289 } 290 } 291 292 @Override 293 public void presentHud(int selectedMediaCount) { 294 MediaCountIndicatorButton countButton = controlsContainer.findViewById(R.id.camera_review_button); 295 View cameraGalleryContainer = controlsContainer.findViewById(R.id.camera_gallery_button_background); 296 297 if (selectedMediaCount > 0) { 298 countButton.setVisibility(View.VISIBLE); 299 countButton.setCount(selectedMediaCount); 300 cameraGalleryContainer.setVisibility(View.GONE); 301 } else { 302 countButton.setVisibility(View.GONE); 303 cameraGalleryContainer.setVisibility(View.VISIBLE); 304 } 305 306 isMediaSelected = selectedMediaCount > 0; 307 updateGalleryVisibility(); 308 } 309 310 private void updateGalleryVisibility() { 311 View cameraGalleryContainer = controlsContainer.findViewById(R.id.camera_gallery_button_background); 312 313 if (isMediaSelected) { 314 cameraGalleryContainer.setVisibility(View.GONE); 315 } else { 316 cameraGalleryContainer.setVisibility(View.VISIBLE); 317 } 318 } 319 320 private void initControls() { 321 flipButton = requireView().findViewById(R.id.camera_flip_button); 322 captureButton = requireView().findViewById(R.id.camera_capture_button); 323 324 View galleryButton = requireView().findViewById(R.id.camera_gallery_button); 325 View countButton = requireView().findViewById(R.id.camera_review_button); 326 327 mostRecentItemDisposable.dispose(); 328 mostRecentItemDisposable = controller.getMostRecentMediaItem() 329 .observeOn(AndroidSchedulers.mainThread()) 330 .subscribe(item -> presentRecentItemThumbnail(item.orElse(null))); 331 332 initializeViewFinderAndControlsPositioning(); 333 334 captureButton.setOnClickListener(v -> { 335 captureButton.setEnabled(false); 336 onCaptureClicked(); 337 }); 338 339 captureButton.setOnLongClickListener(unused -> { 340 CameraFragment.toastVideoRecordingNotAvailable(requireContext()); 341 return true; 342 }); 343 344 orderEnforcer.run(Stage.CAMERA_PROPERTIES_AVAILABLE, () -> { 345 if (properties.getCameraCount() > 1) { 346 flipButton.setVisibility(properties.getCameraCount() > 1 ? View.VISIBLE : View.GONE); 347 flipButton.setOnClickListener(v -> { 348 int newCameraId = camera.flip(); 349 TextSecurePreferences.setDirectCaptureCameraId(getContext(), newCameraId); 350 351 Animation animation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); 352 animation.setDuration(200); 353 animation.setInterpolator(new DecelerateInterpolator()); 354 flipButton.startAnimation(animation); 355 cameraScreenBrightnessController.onCameraDirectionChanged(newCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT); 356 }); 357 } else { 358 flipButton.setVisibility(View.GONE); 359 } 360 }); 361 362 galleryButton.setOnClickListener(v -> controller.onGalleryClicked()); 363 countButton.setOnClickListener(v -> controller.onCameraCountButtonClicked()); 364 } 365 366 private void initializeViewFinderAndControlsPositioning() { 367 MaterialCardView cameraCard = requireView().findViewById(R.id.camera_preview_parent); 368 View controls = requireView().findViewById(R.id.camera_controls_container); 369 CameraDisplay cameraDisplay = CameraDisplay.getDisplay(requireActivity()); 370 371 if (!cameraDisplay.getRoundViewFinderCorners()) { 372 cameraCard.setRadius(0f); 373 } 374 375 ViewUtil.setBottomMargin(controls, cameraDisplay.getCameraCaptureMarginBottom(getResources())); 376 377 if (cameraDisplay.getCameraViewportGravity() == CameraDisplay.CameraViewportGravity.CENTER) { 378 ConstraintSet constraintSet = new ConstraintSet(); 379 constraintSet.clone((ConstraintLayout) requireView()); 380 constraintSet.connect(R.id.camera_preview_parent, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP); 381 constraintSet.applyTo((ConstraintLayout) requireView()); 382 } else { 383 ViewUtil.setBottomMargin(cameraCard, cameraDisplay.getCameraViewportMarginBottom()); 384 } 385 } 386 387 private void onCaptureClicked() { 388 orderEnforcer.reset(); 389 390 Stopwatch fastCaptureTimer = new Stopwatch("Capture"); 391 392 camera.capture((jpegData, frontFacing) -> { 393 fastCaptureTimer.split("captured"); 394 395 Transformation<Bitmap> transformation = frontFacing ? new MultiTransformation<>(new CenterCrop(), new FlipTransformation()) 396 : new CenterCrop(); 397 398 Glide.with(this) 399 .asBitmap() 400 .load(jpegData) 401 .transform(transformation) 402 .override(cameraPreview.getWidth(), cameraPreview.getHeight()) 403 .into(new SimpleTarget<Bitmap>() { 404 @Override 405 public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) { 406 fastCaptureTimer.split("transform"); 407 408 ByteArrayOutputStream stream = new ByteArrayOutputStream(); 409 resource.compress(Bitmap.CompressFormat.JPEG, 80, stream); 410 fastCaptureTimer.split("compressed"); 411 412 byte[] data = stream.toByteArray(); 413 fastCaptureTimer.split("bytes"); 414 fastCaptureTimer.stop(TAG); 415 416 controller.onImageCaptured(data, resource.getWidth(), resource.getHeight()); 417 } 418 419 @Override 420 public void onLoadFailed(@Nullable Drawable errorDrawable) { 421 controller.onCameraError(); 422 } 423 }); 424 }); 425 } 426 427 private PointF getScaleTransform(float viewWidth, float viewHeight, int cameraWidth, int cameraHeight) { 428 float camWidth = isPortrait() ? Math.min(cameraWidth, cameraHeight) : Math.max(cameraWidth, cameraHeight); 429 float camHeight = isPortrait() ? Math.max(cameraWidth, cameraHeight) : Math.min(cameraWidth, cameraHeight); 430 431 float scaleX = 1; 432 float scaleY = 1; 433 434 if ((camWidth / viewWidth) > (camHeight / viewHeight)) { 435 float targetWidth = viewHeight * (camWidth / camHeight); 436 scaleX = targetWidth / viewWidth; 437 } else { 438 float targetHeight = viewWidth * (camHeight / camWidth); 439 scaleY = targetHeight / viewHeight; 440 } 441 442 return new PointF(scaleX, scaleY); 443 } 444 445 private void onOrientationChanged() { 446 int layout = R.layout.camera_controls_portrait; 447 448 controlsContainer.removeAllViews(); 449 controlsContainer.addView(LayoutInflater.from(getContext()).inflate(layout, controlsContainer, false)); 450 initControls(); 451 } 452 453 private void updatePreviewScale() { 454 PointF scale = getScaleTransform(cameraPreview.getWidth(), cameraPreview.getHeight(), properties.getPreviewWidth(), properties.getPreviewHeight()); 455 Matrix matrix = new Matrix(); 456 457 float camWidth = isPortrait() ? Math.min(cameraPreview.getWidth(), cameraPreview.getHeight()) : Math.max(cameraPreview.getWidth(), cameraPreview.getHeight()); 458 float camHeight = isPortrait() ? Math.max(cameraPreview.getWidth(), cameraPreview.getHeight()) : Math.min(cameraPreview.getWidth(), cameraPreview.getHeight()); 459 460 matrix.setScale(scale.x, scale.y); 461 matrix.postTranslate((camWidth - (camWidth * scale.x)) / 2, (camHeight - (camHeight * scale.y)) / 2); 462 cameraPreview.setTransform(matrix); 463 } 464 465 private boolean isPortrait() { 466 return getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; 467 } 468 469 private final GestureDetector.OnGestureListener flipGestureListener = new GestureDetector.SimpleOnGestureListener() { 470 @Override 471 public boolean onDown(MotionEvent e) { 472 return true; 473 } 474 475 @Override 476 public boolean onDoubleTap(MotionEvent e) { 477 flipButton.performClick(); 478 return true; 479 } 480 }; 481 482 private enum Stage { 483 SURFACE_AVAILABLE, CAMERA_PROPERTIES_AVAILABLE 484 } 485 486 private static class CameraStateProvider implements CameraScreenBrightnessController.CameraStateProvider { 487 488 private final Camera1Controller camera1Controller; 489 490 private CameraStateProvider(Camera1Controller camera1Controller) { 491 this.camera1Controller = camera1Controller; 492 } 493 494 @Override 495 public boolean isFrontFacingCameraSelected() { 496 return camera1Controller.isCameraFacingFront(); 497 } 498 499 @Override 500 public boolean isFlashEnabled() { 501 return false; 502 } 503 } 504}