mirror of
https://github.com/deltachat/deltachat-android.git
synced 2025-10-03 09:49:21 +02:00
remove deprecated half-camera dependency
This commit is contained in:
parent
cd07f3de96
commit
5ce1f415ce
11 changed files with 3 additions and 1448 deletions
|
@ -184,7 +184,6 @@ dependencies {
|
|||
annotationProcessor 'androidx.annotation:annotation:1.9.1'
|
||||
implementation 'com.makeramen:roundedimageview:2.1.0' // crops the avatars to circles
|
||||
implementation 'com.pnikosis:materialish-progress:1.5' // used only in the "Progress Wheel" in Share Activity.
|
||||
implementation 'com.nineoldandroids:library:2.4.0' // DEPRECATED! Used to slide in the half-camera.
|
||||
implementation 'com.github.amulyakhare:TextDrawable:558677ea31' // number of unread messages,
|
||||
// the one-letter circle for the contacts (when there is not avatar) and a white background.
|
||||
implementation 'com.googlecode.mp4parser:isoparser:1.0.6' // MP4 recoding; upgrading eg. to 1.1.22 breaks recoding, however, i have not investigated further, just reset to 1.0.6
|
||||
|
|
|
@ -88,9 +88,6 @@ import org.thoughtcrime.securesms.components.InputPanel;
|
|||
import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout.OnKeyboardShownListener;
|
||||
import org.thoughtcrime.securesms.components.ScaleStableImageView;
|
||||
import org.thoughtcrime.securesms.components.SendButton;
|
||||
import org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer;
|
||||
import org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer.AttachmentDrawerListener;
|
||||
import org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer.DrawerState;
|
||||
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard;
|
||||
import org.thoughtcrime.securesms.connect.AccountManager;
|
||||
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
||||
|
@ -142,7 +139,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||
SearchView.OnQueryTextListener,
|
||||
DcEventCenter.DcEventDelegate,
|
||||
OnKeyboardShownListener,
|
||||
AttachmentDrawerListener,
|
||||
InputPanel.Listener,
|
||||
InputPanel.MediaListener
|
||||
{
|
||||
|
@ -181,7 +177,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||
private FrameLayout emojiPickerContainer;
|
||||
private MediaKeyboard emojiPicker;
|
||||
protected HidingLinearLayout quickAttachmentToggle;
|
||||
private QuickAttachmentDrawer quickAttachmentDrawer;
|
||||
private InputPanel inputPanel;
|
||||
|
||||
private ApplicationContext context;
|
||||
|
@ -291,7 +286,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
quickAttachmentDrawer.onResume();
|
||||
|
||||
initializeEnabledCheck();
|
||||
composeText.setTransport(sendButton.getSelectedTransport());
|
||||
|
@ -311,7 +305,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||
|
||||
DcHelper.getNotificationCenter(this).clearVisibleChat();
|
||||
if (isFinishing()) overridePendingTransition(R.anim.fade_scale_in, R.anim.slide_to_right);
|
||||
quickAttachmentDrawer.onPause();
|
||||
inputPanel.onPause();
|
||||
AudioSlidePlayer.stopAll();
|
||||
}
|
||||
|
@ -321,7 +314,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||
Log.i(TAG, "onConfigurationChanged(" + newConfig.orientation + ")");
|
||||
super.onConfigurationChanged(newConfig);
|
||||
composeText.setTransport(sendButton.getSelectedTransport());
|
||||
quickAttachmentDrawer.onConfigurationChanged();
|
||||
|
||||
if (emojiPicker != null && container.getCurrentInput() == emojiPicker) {
|
||||
container.hideAttachedInput(true);
|
||||
|
@ -817,7 +809,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||
emojiPickerContainer = ViewUtil.findById(this, R.id.emoji_picker_container);
|
||||
composePanel = ViewUtil.findById(this, R.id.bottom_panel);
|
||||
container = ViewUtil.findById(this, R.id.layout_container);
|
||||
quickAttachmentDrawer = ViewUtil.findById(this, R.id.quick_attachment_drawer);
|
||||
quickAttachmentToggle = ViewUtil.findById(this, R.id.quick_attachment_toggle);
|
||||
inputPanel = ViewUtil.findById(this, R.id.bottom_panel);
|
||||
backgroundView = ViewUtil.findById(this, R.id.conversation_background);
|
||||
|
@ -857,8 +848,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||
composeText.setOnClickListener(composeKeyPressedListener);
|
||||
composeText.setOnFocusChangeListener(composeKeyPressedListener);
|
||||
|
||||
quickAttachmentDrawer.setListener(this);
|
||||
quickCameraToggle.setOnClickListener(new QuickCameraToggleListener());
|
||||
quickCameraToggle.setOnClickListener(v -> attachmentManager.capturePhoto(ConversationActivity.this, TAKE_PHOTO));
|
||||
|
||||
initializeBackground();
|
||||
}
|
||||
|
@ -1159,43 +1149,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachmentDrawerStateChanged(DrawerState drawerState) {
|
||||
ActionBar supportActionBar = getSupportActionBar();
|
||||
if (supportActionBar == null) throw new AssertionError();
|
||||
|
||||
if (drawerState == DrawerState.FULL_EXPANDED) {
|
||||
supportActionBar.hide();
|
||||
} else {
|
||||
supportActionBar.show();
|
||||
}
|
||||
|
||||
if (drawerState == DrawerState.COLLAPSED) {
|
||||
container.hideAttachedInput(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onImageCapture(@NonNull final byte[] imageBytes) {
|
||||
setMedia(PersistentBlobProvider.getInstance()
|
||||
.create(this, imageBytes, MediaUtil.IMAGE_JPEG, "image.jpeg"),
|
||||
MediaType.IMAGE);
|
||||
quickAttachmentDrawer.hide(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraFail() {
|
||||
Toast.makeText(this, R.string.chat_camera_unavailable, Toast.LENGTH_SHORT).show();
|
||||
quickAttachmentDrawer.hide(false);
|
||||
quickAttachmentToggle.disable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraStart() {}
|
||||
|
||||
@Override
|
||||
public void onCameraStop() {}
|
||||
|
||||
@Override
|
||||
public void onRecorderPermissionRequired() {
|
||||
Permissions.with(this)
|
||||
|
@ -1350,31 +1303,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||
}
|
||||
}
|
||||
|
||||
private class QuickCameraToggleListener implements OnClickListener {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
fragment.hideAddReactionView();
|
||||
if (Prefs.isBuiltInCameraPreferred(ConversationActivity.this)
|
||||
&& QuickAttachmentDrawer.isDeviceSupported(ConversationActivity.this)) {
|
||||
if (!quickAttachmentDrawer.isShowing()) {
|
||||
Permissions.with(ConversationActivity.this)
|
||||
.request(Manifest.permission.CAMERA)
|
||||
.ifNecessary()
|
||||
.withPermanentDenialDialog(getString(R.string.perm_explain_access_to_camera_denied))
|
||||
.onAllGranted(() -> {
|
||||
composeText.clearFocus();
|
||||
container.show(composeText, quickAttachmentDrawer);
|
||||
})
|
||||
.execute();
|
||||
} else {
|
||||
container.hideAttachedInput(false);
|
||||
}
|
||||
} else {
|
||||
attachmentManager.capturePhoto(ConversationActivity.this, TAKE_PHOTO);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SendButtonListener implements OnClickListener, TextView.OnEditorActionListener {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
package org.thoughtcrime.securesms.components.camera;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
|
||||
public class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
|
||||
private boolean ready;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public CameraSurfaceView(Context context) {
|
||||
super(context);
|
||||
getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
|
||||
getHolder().addCallback(this);
|
||||
}
|
||||
|
||||
public boolean isReady() {
|
||||
return ready;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
ready = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
ready = false;
|
||||
}
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
package org.thoughtcrime.securesms.components.camera;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.hardware.Camera;
|
||||
import android.hardware.Camera.CameraInfo;
|
||||
import android.hardware.Camera.Parameters;
|
||||
import android.hardware.Camera.Size;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.Surface;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class CameraUtils {
|
||||
private static final String TAG = CameraUtils.class.getSimpleName();
|
||||
/*
|
||||
* modified from: https://github.com/commonsguy/cwac-camera/blob/master/camera/src/com/commonsware/cwac/camera/CameraUtils.java
|
||||
*/
|
||||
public static @Nullable Size getPreferredPreviewSize(int displayOrientation,
|
||||
int width,
|
||||
int height,
|
||||
@NonNull Parameters parameters) {
|
||||
final int targetWidth = displayOrientation % 180 == 90 ? height : width;
|
||||
final int targetHeight = displayOrientation % 180 == 90 ? width : height;
|
||||
final double targetRatio = (double) targetWidth / targetHeight;
|
||||
|
||||
Log.w(TAG, String.format("getPreferredPreviewSize(%d, %d, %d) -> target %dx%d, AR %.02f",
|
||||
displayOrientation, width, height,
|
||||
targetWidth, targetHeight, targetRatio));
|
||||
|
||||
List<Size> sizes = parameters.getSupportedPreviewSizes();
|
||||
List<Size> ideals = new LinkedList<>();
|
||||
List<Size> bigEnough = new LinkedList<>();
|
||||
|
||||
for (Size size : sizes) {
|
||||
Log.w(TAG, String.format(" %dx%d (%.02f)", size.width, size.height, (float)size.width / size.height));
|
||||
|
||||
if (size.height == size.width * targetRatio && size.height >= targetHeight && size.width >= targetWidth) {
|
||||
ideals.add(size);
|
||||
Log.w(TAG, " (ideal ratio)");
|
||||
} else if (size.width >= targetWidth && size.height >= targetHeight) {
|
||||
bigEnough.add(size);
|
||||
Log.w(TAG, " (good size, suboptimal ratio)");
|
||||
}
|
||||
}
|
||||
|
||||
if (!ideals.isEmpty()) return Collections.min(ideals, new AreaComparator());
|
||||
else if (!bigEnough.isEmpty()) return Collections.min(bigEnough, new AspectRatioComparator(targetRatio));
|
||||
else return Collections.max(sizes, new AreaComparator());
|
||||
}
|
||||
|
||||
// based on
|
||||
// http://developer.android.com/reference/android/hardware/Camera.html#setDisplayOrientation(int)
|
||||
// and http://stackoverflow.com/a/10383164/115145
|
||||
public static int getCameraDisplayOrientation(@NonNull Activity activity,
|
||||
@NonNull CameraInfo info)
|
||||
{
|
||||
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
|
||||
int degrees = 0;
|
||||
DisplayMetrics dm = new DisplayMetrics();
|
||||
|
||||
activity.getWindowManager().getDefaultDisplay().getMetrics(dm);
|
||||
|
||||
switch (rotation) {
|
||||
case Surface.ROTATION_0: degrees = 0; break;
|
||||
case Surface.ROTATION_90: degrees = 90; break;
|
||||
case Surface.ROTATION_180: degrees = 180; break;
|
||||
case Surface.ROTATION_270: degrees = 270; break;
|
||||
}
|
||||
|
||||
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
|
||||
return (360 - ((info.orientation + degrees) % 360)) % 360;
|
||||
} else {
|
||||
return (info.orientation - degrees + 360) % 360;
|
||||
}
|
||||
}
|
||||
|
||||
private static class AreaComparator implements Comparator<Size> {
|
||||
@Override
|
||||
public int compare(Size lhs, Size rhs) {
|
||||
return Long.signum(lhs.width * lhs.height - rhs.width * rhs.height);
|
||||
}
|
||||
}
|
||||
|
||||
private static class AspectRatioComparator extends AreaComparator {
|
||||
private final double target;
|
||||
public AspectRatioComparator(double target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(Size lhs, Size rhs) {
|
||||
final double lhsDiff = Math.abs(target - (double) lhs.width / lhs.height);
|
||||
final double rhsDiff = Math.abs(target - (double) rhs.width / rhs.height);
|
||||
if (lhsDiff < rhsDiff) return -1;
|
||||
else if (lhsDiff > rhsDiff) return 1;
|
||||
else return super.compare(lhs, rhs);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,625 +0,0 @@
|
|||
/***
|
||||
Copyright (c) 2013-2014 CommonsWare, LLC
|
||||
Portions Copyright (C) 2007 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
not use this file except in compliance with the License. You may obtain
|
||||
a copy of the License at
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.camera;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.hardware.Camera;
|
||||
import android.hardware.Camera.CameraInfo;
|
||||
import android.hardware.Camera.Parameters;
|
||||
import android.hardware.Camera.Size;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Build.VERSION;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.OrientationEventListener;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobParameters;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
import org.thoughtcrime.securesms.util.Prefs;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.guava.Optional;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class CameraView extends ViewGroup {
|
||||
private static final String TAG = CameraView.class.getSimpleName();
|
||||
|
||||
private final CameraSurfaceView surface;
|
||||
private final OnOrientationChange onOrientationChange;
|
||||
|
||||
private volatile Optional<Camera> camera = Optional.absent();
|
||||
private volatile int cameraId = CameraInfo.CAMERA_FACING_BACK;
|
||||
private volatile int displayOrientation = -1;
|
||||
|
||||
private @NonNull State state = State.PAUSED;
|
||||
private @Nullable Size previewSize;
|
||||
private final @NonNull List<CameraViewListener> listeners = Collections.synchronizedList(new LinkedList<CameraViewListener>());
|
||||
private int outputOrientation = -1;
|
||||
|
||||
public CameraView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public CameraView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public CameraView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
setBackgroundColor(Color.BLACK);
|
||||
|
||||
if (attrs != null) {
|
||||
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CameraView);
|
||||
int camera = typedArray.getInt(R.styleable.CameraView_camera, -1);
|
||||
|
||||
if (camera != -1) cameraId = camera;
|
||||
else if (isMultiCamera()) cameraId = Prefs.getDirectCaptureCameraId(context);
|
||||
|
||||
typedArray.recycle();
|
||||
}
|
||||
|
||||
surface = new CameraSurfaceView(getContext());
|
||||
onOrientationChange = new OnOrientationChange(context.getApplicationContext());
|
||||
addView(surface);
|
||||
}
|
||||
|
||||
public void onResume() {
|
||||
if (state != State.PAUSED) return;
|
||||
state = State.RESUMED;
|
||||
Log.w(TAG, "onResume() queued");
|
||||
enqueueTask(new SerialAsyncTask<Void>() {
|
||||
@Override
|
||||
protected
|
||||
@Nullable
|
||||
Void onRunBackground() {
|
||||
try {
|
||||
long openStartMillis = System.currentTimeMillis();
|
||||
camera = Optional.fromNullable(Camera.open(cameraId));
|
||||
Log.w(TAG, "camera.open() -> " + (System.currentTimeMillis() - openStartMillis) + "ms");
|
||||
synchronized (CameraView.this) {
|
||||
CameraView.this.notifyAll();
|
||||
}
|
||||
if (camera.isPresent()) onCameraReady(camera.get());
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostMain(Void avoid) {
|
||||
if (!camera.isPresent()) {
|
||||
Log.w(TAG, "tried to open camera but got null");
|
||||
for (CameraViewListener listener : listeners) {
|
||||
listener.onCameraFail();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (getActivity().getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
|
||||
onOrientationChange.enable();
|
||||
}
|
||||
Log.w(TAG, "onResume() completed");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void onPause() {
|
||||
if (state == State.PAUSED) return;
|
||||
state = State.PAUSED;
|
||||
Log.w(TAG, "onPause() queued");
|
||||
|
||||
enqueueTask(new SerialAsyncTask<Void>() {
|
||||
private Optional<Camera> cameraToDestroy;
|
||||
|
||||
@Override
|
||||
protected void onPreMain() {
|
||||
cameraToDestroy = camera;
|
||||
camera = Optional.absent();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void onRunBackground() {
|
||||
if (cameraToDestroy.isPresent()) {
|
||||
try {
|
||||
stopPreview();
|
||||
cameraToDestroy.get().setPreviewCallback(null);
|
||||
cameraToDestroy.get().release();
|
||||
Log.w(TAG, "released old camera instance");
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override protected void onPostMain(Void avoid) {
|
||||
onOrientationChange.disable();
|
||||
displayOrientation = -1;
|
||||
outputOrientation = -1;
|
||||
removeView(surface);
|
||||
addView(surface);
|
||||
Log.w(TAG, "onPause() completed");
|
||||
}
|
||||
});
|
||||
|
||||
for (CameraViewListener listener : listeners) {
|
||||
listener.onCameraStop();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isStarted() {
|
||||
return state != State.PAUSED;
|
||||
}
|
||||
|
||||
@SuppressWarnings("SuspiciousNameCombination")
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
final int width = r - l;
|
||||
final int height = b - t;
|
||||
final int previewWidth;
|
||||
final int previewHeight;
|
||||
|
||||
if (camera.isPresent() && previewSize != null) {
|
||||
if (displayOrientation == 90 || displayOrientation == 270) {
|
||||
previewWidth = previewSize.height;
|
||||
previewHeight = previewSize.width;
|
||||
} else {
|
||||
previewWidth = previewSize.width;
|
||||
previewHeight = previewSize.height;
|
||||
}
|
||||
} else {
|
||||
previewWidth = width;
|
||||
previewHeight = height;
|
||||
}
|
||||
|
||||
if (previewHeight == 0 || previewWidth == 0) {
|
||||
Log.i(TAG, "skipping layout due to zero-width/height preview size");
|
||||
return;
|
||||
}
|
||||
|
||||
if (width * previewHeight > height * previewWidth) {
|
||||
final int scaledChildHeight = previewHeight * width / previewWidth;
|
||||
surface.layout(0, (height - scaledChildHeight) / 2, width, (height + scaledChildHeight) / 2);
|
||||
} else {
|
||||
final int scaledChildWidth = previewWidth * height / previewHeight;
|
||||
surface.layout((width - scaledChildWidth) / 2, 0, (width + scaledChildWidth) / 2, height);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
Log.w(TAG, "onSizeChanged(" + oldw + "x" + oldh + " -> " + w + "x" + h + ")");
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
if (camera.isPresent()) startPreview(camera.get().getParameters());
|
||||
}
|
||||
|
||||
public void addListener(@NonNull CameraViewListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
public void setPreviewCallback(final @NonNull PreviewCallback previewCallback) {
|
||||
enqueueTask(new PostInitializationTask<Void>() {
|
||||
@Override
|
||||
protected void onPostMain(Void avoid) {
|
||||
if (camera.isPresent()) {
|
||||
camera.get().setPreviewCallback(new Camera.PreviewCallback() {
|
||||
@Override
|
||||
public void onPreviewFrame(byte[] data, Camera camera) {
|
||||
if (!CameraView.this.camera.isPresent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int rotation = getCameraPictureOrientation();
|
||||
final Size previewSize = camera.getParameters().getPreviewSize();
|
||||
if (data != null) {
|
||||
previewCallback.onPreviewFrame(new PreviewFrame(data, previewSize.width, previewSize.height, rotation));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public boolean isMultiCamera() {
|
||||
return Camera.getNumberOfCameras() > 1;
|
||||
}
|
||||
|
||||
public boolean isRearCamera() {
|
||||
return cameraId == CameraInfo.CAMERA_FACING_BACK;
|
||||
}
|
||||
|
||||
public void flipCamera() {
|
||||
if (Camera.getNumberOfCameras() > 1) {
|
||||
cameraId = cameraId == CameraInfo.CAMERA_FACING_BACK
|
||||
? CameraInfo.CAMERA_FACING_FRONT
|
||||
: CameraInfo.CAMERA_FACING_BACK;
|
||||
onPause();
|
||||
onResume();
|
||||
Prefs.setDirectCaptureCameraId(getContext(), cameraId);
|
||||
}
|
||||
}
|
||||
|
||||
private void onCameraReady(final @NonNull Camera camera) {
|
||||
final Parameters parameters = camera.getParameters();
|
||||
|
||||
parameters.setRecordingHint(true);
|
||||
final List<String> focusModes = parameters.getSupportedFocusModes();
|
||||
if (focusModes.contains(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
|
||||
parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
|
||||
} else if (focusModes.contains(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
|
||||
parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
|
||||
}
|
||||
|
||||
displayOrientation = CameraUtils.getCameraDisplayOrientation(getActivity(), getCameraInfo());
|
||||
camera.setDisplayOrientation(displayOrientation);
|
||||
camera.setParameters(parameters);
|
||||
enqueueTask(new PostInitializationTask<Void>() {
|
||||
@Override
|
||||
protected Void onRunBackground() {
|
||||
try {
|
||||
camera.setPreviewDisplay(surface.getHolder());
|
||||
startPreview(parameters);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "couldn't set preview display", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void startPreview(final @NonNull Parameters parameters) {
|
||||
if (this.camera.isPresent()) {
|
||||
try {
|
||||
final Camera camera = this.camera.get();
|
||||
final Size preferredPreviewSize = getPreferredPreviewSize(parameters);
|
||||
|
||||
if (preferredPreviewSize != null && !parameters.getPreviewSize().equals(preferredPreviewSize)) {
|
||||
Log.w(TAG, "starting preview with size " + preferredPreviewSize.width + "x" + preferredPreviewSize.height);
|
||||
if (state == State.ACTIVE) stopPreview();
|
||||
previewSize = preferredPreviewSize;
|
||||
parameters.setPreviewSize(preferredPreviewSize.width, preferredPreviewSize.height);
|
||||
camera.setParameters(parameters);
|
||||
} else {
|
||||
previewSize = parameters.getPreviewSize();
|
||||
}
|
||||
long previewStartMillis = System.currentTimeMillis();
|
||||
camera.startPreview();
|
||||
Log.w(TAG, "camera.startPreview() -> " + (System.currentTimeMillis() - previewStartMillis) + "ms");
|
||||
state = State.ACTIVE;
|
||||
Util.runOnMain(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
requestLayout();
|
||||
for (CameraViewListener listener : listeners) {
|
||||
listener.onCameraStart();
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void stopPreview() {
|
||||
if (camera.isPresent()) {
|
||||
try {
|
||||
camera.get().stopPreview();
|
||||
state = State.RESUMED;
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Size getPreferredPreviewSize(@NonNull Parameters parameters) {
|
||||
return CameraUtils.getPreferredPreviewSize(displayOrientation,
|
||||
getMeasuredWidth(),
|
||||
getMeasuredHeight(),
|
||||
parameters);
|
||||
}
|
||||
|
||||
private int getCameraPictureOrientation() {
|
||||
if (getActivity().getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
|
||||
outputOrientation = getCameraPictureRotation(getActivity().getWindowManager()
|
||||
.getDefaultDisplay()
|
||||
.getOrientation());
|
||||
} else if (getCameraInfo().facing == CameraInfo.CAMERA_FACING_FRONT) {
|
||||
outputOrientation = (360 - displayOrientation) % 360;
|
||||
} else {
|
||||
outputOrientation = displayOrientation;
|
||||
}
|
||||
|
||||
return outputOrientation;
|
||||
}
|
||||
|
||||
// https://github.com/signalapp/Signal-Android/issues/4715
|
||||
private boolean isTroublemaker() {
|
||||
return getCameraInfo().facing == CameraInfo.CAMERA_FACING_FRONT &&
|
||||
"JWR66Y".equals(Build.DISPLAY) &&
|
||||
"yakju".equals(Build.PRODUCT);
|
||||
}
|
||||
|
||||
private @NonNull CameraInfo getCameraInfo() {
|
||||
final CameraInfo info = new Camera.CameraInfo();
|
||||
Camera.getCameraInfo(cameraId, info);
|
||||
return info;
|
||||
}
|
||||
|
||||
// XXX this sucks
|
||||
private Activity getActivity() {
|
||||
return (Activity)getContext();
|
||||
}
|
||||
|
||||
public int getCameraPictureRotation(int orientation) {
|
||||
final CameraInfo info = getCameraInfo();
|
||||
final int rotation;
|
||||
|
||||
orientation = (orientation + 45) / 90 * 90;
|
||||
|
||||
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
|
||||
rotation = (info.orientation - orientation + 360) % 360;
|
||||
} else {
|
||||
rotation = (info.orientation + orientation) % 360;
|
||||
}
|
||||
|
||||
return rotation;
|
||||
}
|
||||
|
||||
private class OnOrientationChange extends OrientationEventListener {
|
||||
public OnOrientationChange(Context context) {
|
||||
super(context);
|
||||
disable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOrientationChanged(int orientation) {
|
||||
if (camera.isPresent() && orientation != ORIENTATION_UNKNOWN) {
|
||||
int newOutputOrientation = getCameraPictureRotation(orientation);
|
||||
|
||||
if (newOutputOrientation != outputOrientation) {
|
||||
outputOrientation = newOutputOrientation;
|
||||
|
||||
Camera.Parameters params = camera.get().getParameters();
|
||||
|
||||
params.setRotation(outputOrientation);
|
||||
|
||||
try {
|
||||
camera.get().setParameters(params);
|
||||
}
|
||||
catch (Exception e) {
|
||||
Log.e(TAG, "Exception updating camera parameters in orientation change", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void takePicture(final Rect previewRect) {
|
||||
if (!camera.isPresent() || camera.get().getParameters() == null) {
|
||||
Log.w(TAG, "camera not in capture-ready state");
|
||||
return;
|
||||
}
|
||||
|
||||
camera.get().setOneShotPreviewCallback(new Camera.PreviewCallback() {
|
||||
@Override
|
||||
public void onPreviewFrame(byte[] data, final Camera camera) {
|
||||
final int rotation = getCameraPictureOrientation();
|
||||
final Size previewSize = camera.getParameters().getPreviewSize();
|
||||
final Rect croppingRect = getCroppedRect(previewSize, previewRect, rotation);
|
||||
|
||||
Log.w(TAG, "previewSize: " + previewSize.width + "x" + previewSize.height);
|
||||
Log.w(TAG, "data bytes: " + data.length);
|
||||
Log.w(TAG, "previewFormat: " + camera.getParameters().getPreviewFormat());
|
||||
Log.w(TAG, "croppingRect: " + croppingRect.toString());
|
||||
Log.w(TAG, "rotation: " + rotation);
|
||||
new CaptureTask(previewSize, rotation, croppingRect).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Rect getCroppedRect(Size cameraPreviewSize, Rect visibleRect, int rotation) {
|
||||
final int previewWidth = cameraPreviewSize.width;
|
||||
final int previewHeight = cameraPreviewSize.height;
|
||||
|
||||
if (rotation % 180 > 0) rotateRect(visibleRect);
|
||||
|
||||
float scale = (float) previewWidth / visibleRect.width();
|
||||
if (visibleRect.height() * scale > previewHeight) {
|
||||
scale = (float) previewHeight / visibleRect.height();
|
||||
}
|
||||
final float newWidth = visibleRect.width() * scale;
|
||||
final float newHeight = visibleRect.height() * scale;
|
||||
final float centerX = isTroublemaker() ? previewWidth - newWidth / 2 : previewWidth / 2;
|
||||
final float centerY = previewHeight / 2;
|
||||
|
||||
visibleRect.set((int) (centerX - newWidth / 2),
|
||||
(int) (centerY - newHeight / 2),
|
||||
(int) (centerX + newWidth / 2),
|
||||
(int) (centerY + newHeight / 2));
|
||||
|
||||
if (rotation % 180 > 0) rotateRect(visibleRect);
|
||||
return visibleRect;
|
||||
}
|
||||
|
||||
@SuppressWarnings("SuspiciousNameCombination")
|
||||
private void rotateRect(Rect rect) {
|
||||
rect.set(rect.top, rect.left, rect.bottom, rect.right);
|
||||
}
|
||||
|
||||
private void enqueueTask(SerialAsyncTask job) {
|
||||
ApplicationContext.getInstance(getContext()).getJobManager().add(job);
|
||||
}
|
||||
|
||||
private static abstract class SerialAsyncTask<Result> extends Job {
|
||||
|
||||
public SerialAsyncTask() {
|
||||
super(JobParameters.newBuilder().withGroupId(CameraView.class.getSimpleName()).create());
|
||||
}
|
||||
|
||||
@Override public void onAdded() {}
|
||||
|
||||
@Override public final void onRun() {
|
||||
try {
|
||||
onWait();
|
||||
Util.runOnMainSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
onPreMain();
|
||||
}
|
||||
});
|
||||
|
||||
final Result result = onRunBackground();
|
||||
|
||||
Util.runOnMainSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
onPostMain(result);
|
||||
}
|
||||
});
|
||||
} catch (PreconditionsNotMetException e) {
|
||||
Log.w(TAG, "skipping task, preconditions not met in onWait()");
|
||||
}
|
||||
}
|
||||
|
||||
@Override public boolean onShouldRetry(Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public void onCanceled() { }
|
||||
|
||||
protected void onWait() throws PreconditionsNotMetException {}
|
||||
protected void onPreMain() {}
|
||||
protected Result onRunBackground() { return null; }
|
||||
protected void onPostMain(Result result) {}
|
||||
}
|
||||
|
||||
private abstract class PostInitializationTask<Result> extends SerialAsyncTask<Result> {
|
||||
@Override protected void onWait() throws PreconditionsNotMetException {
|
||||
synchronized (CameraView.this) {
|
||||
if (!camera.isPresent()) {
|
||||
throw new PreconditionsNotMetException();
|
||||
}
|
||||
while (getMeasuredHeight() <= 0 || getMeasuredWidth() <= 0 || !surface.isReady()) {
|
||||
Log.w(TAG, String.format("waiting. surface ready? %s", surface.isReady()));
|
||||
Util.wait(CameraView.this, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CaptureTask extends AsyncTask<byte[], Void, byte[]> {
|
||||
private final Size previewSize;
|
||||
private final int rotation;
|
||||
private final Rect croppingRect;
|
||||
|
||||
public CaptureTask(Size previewSize, int rotation, Rect croppingRect) {
|
||||
this.previewSize = previewSize;
|
||||
this.rotation = rotation;
|
||||
this.croppingRect = croppingRect;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] doInBackground(byte[]... params) {
|
||||
final byte[] data = params[0];
|
||||
try {
|
||||
return BitmapUtil.createFromNV21(data,
|
||||
previewSize.width,
|
||||
previewSize.height,
|
||||
rotation,
|
||||
croppingRect,
|
||||
cameraId == CameraInfo.CAMERA_FACING_FRONT);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(byte[] imageBytes) {
|
||||
if (imageBytes != null) {
|
||||
for (CameraViewListener listener : listeners) {
|
||||
listener.onImageCapture(imageBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class PreconditionsNotMetException extends Exception {}
|
||||
|
||||
public interface CameraViewListener {
|
||||
void onImageCapture(@NonNull final byte[] imageBytes);
|
||||
void onCameraFail();
|
||||
void onCameraStart();
|
||||
void onCameraStop();
|
||||
}
|
||||
|
||||
public interface PreviewCallback {
|
||||
void onPreviewFrame(@NonNull PreviewFrame frame);
|
||||
}
|
||||
|
||||
public static class PreviewFrame {
|
||||
private final @NonNull byte[] data;
|
||||
private final int width;
|
||||
private final int height;
|
||||
private final int orientation;
|
||||
|
||||
private PreviewFrame(@NonNull byte[] data, int width, int height, int orientation) {
|
||||
this.data = data;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.orientation = orientation;
|
||||
}
|
||||
|
||||
public @NonNull byte[] getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public int getOrientation() {
|
||||
return orientation;
|
||||
}
|
||||
}
|
||||
|
||||
private enum State {
|
||||
PAUSED, RESUMED, ACTIVE
|
||||
}
|
||||
}
|
|
@ -1,571 +0,0 @@
|
|||
package org.thoughtcrime.securesms.components.camera;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.hardware.Camera;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.view.MotionEventCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
|
||||
import androidx.customview.widget.ViewDragHelper;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
import com.nineoldandroids.animation.ObjectAnimator;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.InputAwareLayout.InputView;
|
||||
import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout;
|
||||
import org.thoughtcrime.securesms.components.camera.CameraView.CameraViewListener;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public class QuickAttachmentDrawer extends ViewGroup implements InputView, CameraViewListener {
|
||||
private static final String TAG = QuickAttachmentDrawer.class.getSimpleName();
|
||||
|
||||
private final ViewDragHelper dragHelper;
|
||||
|
||||
private CameraView cameraView;
|
||||
private int coverViewPosition;
|
||||
private KeyboardAwareLinearLayout container;
|
||||
private View coverView;
|
||||
private View controls;
|
||||
private ImageButton fullScreenButton;
|
||||
private ImageButton swapCameraButton;
|
||||
private ImageButton shutterButton;
|
||||
private int slideOffset;
|
||||
private float initialMotionX;
|
||||
private float initialMotionY;
|
||||
private int rotation;
|
||||
private AttachmentDrawerListener listener;
|
||||
private int halfExpandedHeight;
|
||||
private ObjectAnimator animator;
|
||||
|
||||
private DrawerState drawerState = DrawerState.COLLAPSED;
|
||||
private final Rect drawChildrenRect = new Rect();
|
||||
private boolean paused = false;
|
||||
|
||||
public QuickAttachmentDrawer(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public QuickAttachmentDrawer(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public QuickAttachmentDrawer(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
dragHelper = ViewDragHelper.create(this, 1.f, new ViewDragHelperCallback());
|
||||
initializeView();
|
||||
updateHalfExpandedAnchorPoint();
|
||||
onConfigurationChanged();
|
||||
}
|
||||
|
||||
private void initializeView() {
|
||||
inflate(getContext(), R.layout.quick_attachment_drawer, this);
|
||||
cameraView = ViewUtil.findById(this, R.id.quick_camera);
|
||||
updateControlsView();
|
||||
|
||||
coverViewPosition = getChildCount();
|
||||
controls.setVisibility(GONE);
|
||||
cameraView.setVisibility(GONE);
|
||||
cameraView.addListener(this);
|
||||
}
|
||||
|
||||
public static boolean isDeviceSupported(Context context) {
|
||||
return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA) &&
|
||||
Camera.getNumberOfCameras() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShowing() {
|
||||
return drawerState.isVisible();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hide(boolean immediate) {
|
||||
setDrawerStateAndUpdate(DrawerState.COLLAPSED, immediate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show(int height, boolean immediate) {
|
||||
setDrawerStateAndUpdate(DrawerState.HALF_EXPANDED, immediate);
|
||||
}
|
||||
|
||||
public void onConfigurationChanged() {
|
||||
int rotation = ServiceUtil.getWindowManager(getContext()).getDefaultDisplay().getRotation();
|
||||
final boolean rotationChanged = this.rotation != rotation;
|
||||
this.rotation = rotation;
|
||||
if (rotationChanged) {
|
||||
updateControlsView();
|
||||
setDrawerStateAndUpdate(drawerState, true);
|
||||
if (isShowing()) {
|
||||
cameraView.onPause();
|
||||
cameraView.onResume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateControlsView() {
|
||||
Log.w(TAG, "updateControlsView()");
|
||||
View controls = LayoutInflater.from(getContext()).inflate(isLandscape() ? R.layout.quick_camera_controls_land
|
||||
: R.layout.quick_camera_controls,
|
||||
this, false);
|
||||
shutterButton = (ImageButton) controls.findViewById(R.id.shutter_button);
|
||||
swapCameraButton = (ImageButton) controls.findViewById(R.id.swap_camera_button);
|
||||
fullScreenButton = (ImageButton) controls.findViewById(R.id.fullscreen_button);
|
||||
if (cameraView.isMultiCamera()) {
|
||||
swapCameraButton.setVisibility(View.VISIBLE);
|
||||
swapCameraButton.setImageResource(cameraView.isRearCamera() ? R.drawable.quick_camera_front
|
||||
: R.drawable.quick_camera_rear);
|
||||
swapCameraButton.setOnClickListener(new CameraFlipClickListener());
|
||||
}
|
||||
shutterButton.setOnClickListener(new ShutterClickListener());
|
||||
fullScreenButton.setOnClickListener(new FullscreenClickListener());
|
||||
ViewUtil.swapChildInPlace(this, this.controls, controls, indexOfChild(cameraView) + 1);
|
||||
this.controls = controls;
|
||||
}
|
||||
|
||||
private boolean isLandscape() {
|
||||
return rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270;
|
||||
}
|
||||
|
||||
private View getCoverView() {
|
||||
if (coverView == null) coverView = getChildAt(coverViewPosition);
|
||||
return coverView;
|
||||
}
|
||||
|
||||
private KeyboardAwareLinearLayout getContainer() {
|
||||
if (container == null) container = (KeyboardAwareLinearLayout)getParent();
|
||||
return container;
|
||||
}
|
||||
|
||||
private void updateHalfExpandedAnchorPoint() {
|
||||
if (getContainer() != null) {
|
||||
halfExpandedHeight = getContainer().getKeyboardHeight();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
if (changed && drawerState == DrawerState.FULL_EXPANDED) {
|
||||
setSlideOffset(getMeasuredHeight());
|
||||
}
|
||||
|
||||
final int paddingLeft = getPaddingLeft();
|
||||
final int paddingTop = getPaddingTop();
|
||||
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
final View child = getChildAt(i);
|
||||
final int childHeight = child.getMeasuredHeight();
|
||||
|
||||
int childTop = paddingTop;
|
||||
int childLeft = paddingLeft;
|
||||
int childBottom;
|
||||
|
||||
if (child == cameraView) {
|
||||
childTop = computeCameraTopPosition(slideOffset);
|
||||
childBottom = childTop + childHeight;
|
||||
if (cameraView.getMeasuredWidth() < getMeasuredWidth())
|
||||
childLeft = (getMeasuredWidth() - cameraView.getMeasuredWidth()) / 2 + paddingLeft;
|
||||
} else if (child == controls) {
|
||||
childBottom = getMeasuredHeight();
|
||||
} else {
|
||||
childTop = computeCoverTopPosition(slideOffset);
|
||||
childBottom = childTop + childHeight;
|
||||
}
|
||||
final int childRight = childLeft + child.getMeasuredWidth();
|
||||
|
||||
child.layout(childLeft, childTop, childRight, childBottom);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
|
||||
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
|
||||
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
|
||||
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
|
||||
|
||||
if (widthMode != MeasureSpec.EXACTLY) {
|
||||
throw new IllegalStateException("Width must have an exact value or MATCH_PARENT");
|
||||
} else if (heightMode != MeasureSpec.EXACTLY) {
|
||||
throw new IllegalStateException("Height must have an exact value or MATCH_PARENT");
|
||||
}
|
||||
|
||||
int layoutHeight = heightSize - getPaddingTop() - getPaddingBottom();
|
||||
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
final View child = getChildAt(i);
|
||||
final LayoutParams lp = child.getLayoutParams();
|
||||
|
||||
if (child.getVisibility() == GONE && i == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int childWidthSpec;
|
||||
switch (lp.width) {
|
||||
case LayoutParams.WRAP_CONTENT:
|
||||
childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST);
|
||||
break;
|
||||
case LayoutParams.MATCH_PARENT:
|
||||
childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
|
||||
break;
|
||||
default:
|
||||
childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
|
||||
break;
|
||||
}
|
||||
|
||||
int childHeightSpec;
|
||||
switch (lp.height) {
|
||||
case LayoutParams.WRAP_CONTENT:
|
||||
childHeightSpec = MeasureSpec.makeMeasureSpec(layoutHeight, MeasureSpec.AT_MOST);
|
||||
break;
|
||||
case LayoutParams.MATCH_PARENT:
|
||||
childHeightSpec = MeasureSpec.makeMeasureSpec(layoutHeight, MeasureSpec.EXACTLY);
|
||||
break;
|
||||
default:
|
||||
childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
|
||||
break;
|
||||
}
|
||||
|
||||
child.measure(childWidthSpec, childHeightSpec);
|
||||
}
|
||||
|
||||
setMeasuredDimension(widthSize, heightSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
if (h != oldh) updateHalfExpandedAnchorPoint();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean drawChild(@NonNull Canvas canvas, @NonNull View child, long drawingTime) {
|
||||
boolean result;
|
||||
final int save = canvas.save();
|
||||
|
||||
canvas.getClipBounds(drawChildrenRect);
|
||||
if (child == coverView) {
|
||||
drawChildrenRect.bottom = Math.min(drawChildrenRect.bottom, child.getBottom());
|
||||
} else if (coverView != null) {
|
||||
drawChildrenRect.top = Math.max(drawChildrenRect.top, coverView.getBottom());
|
||||
}
|
||||
canvas.clipRect(drawChildrenRect);
|
||||
result = super.drawChild(canvas, child, drawingTime);
|
||||
canvas.restoreToCount(save);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void computeScroll() {
|
||||
if (dragHelper.continueSettling(true)) {
|
||||
ViewCompat.postInvalidateOnAnimation(this);
|
||||
}
|
||||
|
||||
if (slideOffset == 0 && cameraView.isStarted()) {
|
||||
cameraView.onPause();
|
||||
controls.setVisibility(GONE);
|
||||
cameraView.setVisibility(GONE);
|
||||
} else if (slideOffset != 0 && !cameraView.isStarted() & !paused) {
|
||||
controls.setVisibility(VISIBLE);
|
||||
cameraView.setVisibility(VISIBLE);
|
||||
cameraView.onResume();
|
||||
}
|
||||
}
|
||||
|
||||
private void setDrawerState(DrawerState drawerState) {
|
||||
switch (drawerState) {
|
||||
case COLLAPSED:
|
||||
fullScreenButton.setImageResource(R.drawable.quick_camera_fullscreen);
|
||||
break;
|
||||
case HALF_EXPANDED:
|
||||
if (isLandscape()) {
|
||||
setDrawerState(DrawerState.FULL_EXPANDED);
|
||||
return;
|
||||
}
|
||||
fullScreenButton.setImageResource(R.drawable.quick_camera_fullscreen);
|
||||
break;
|
||||
case FULL_EXPANDED:
|
||||
fullScreenButton.setImageResource(isLandscape() ? R.drawable.quick_camera_hide
|
||||
: R.drawable.quick_camera_exit_fullscreen);
|
||||
break;
|
||||
}
|
||||
|
||||
if (listener != null && drawerState != this.drawerState) {
|
||||
this.drawerState = drawerState;
|
||||
listener.onAttachmentDrawerStateChanged(drawerState);
|
||||
}
|
||||
}
|
||||
|
||||
public void setSlideOffset(int slideOffset) {
|
||||
this.slideOffset = slideOffset;
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
public int getTargetSlideOffset() {
|
||||
switch (drawerState) {
|
||||
case FULL_EXPANDED: return getMeasuredHeight();
|
||||
case HALF_EXPANDED: return halfExpandedHeight;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void setDrawerStateAndUpdate(final DrawerState requestedDrawerState) {
|
||||
setDrawerStateAndUpdate(requestedDrawerState, false);
|
||||
}
|
||||
|
||||
public void setDrawerStateAndUpdate(final DrawerState requestedDrawerState, boolean instant) {
|
||||
DrawerState oldDrawerState = this.drawerState;
|
||||
setDrawerState(requestedDrawerState);
|
||||
if (oldDrawerState != drawerState) {
|
||||
updateHalfExpandedAnchorPoint();
|
||||
slideTo(getTargetSlideOffset(), instant);
|
||||
}
|
||||
}
|
||||
|
||||
public void setListener(AttachmentDrawerListener listener) {
|
||||
this.listener = listener;
|
||||
cameraView.addListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onImageCapture(@NonNull byte[] imageBytes) {}
|
||||
|
||||
@Override
|
||||
public void onCameraFail() {
|
||||
swapCameraButton.setEnabled(false);
|
||||
shutterButton.setEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraStart() {
|
||||
swapCameraButton.setEnabled(true);
|
||||
shutterButton.setEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraStop() {
|
||||
swapCameraButton.setEnabled(false);
|
||||
shutterButton.setEnabled(false);
|
||||
}
|
||||
|
||||
public interface AttachmentDrawerListener extends CameraViewListener {
|
||||
void onAttachmentDrawerStateChanged(DrawerState drawerState);
|
||||
}
|
||||
|
||||
private class ViewDragHelperCallback extends ViewDragHelper.Callback {
|
||||
|
||||
@Override
|
||||
public boolean tryCaptureView(View child, int pointerId) {
|
||||
return child == controls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDragStateChanged(int state) {
|
||||
if (dragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) {
|
||||
setDrawerState(drawerState);
|
||||
slideOffset = getTargetSlideOffset();
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCaptured(View capturedChild, int activePointerId) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
|
||||
slideOffset = Util.clamp(slideOffset - dy, 0, getMeasuredHeight());
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewReleased(View releasedChild, float xvel, float yvel) {
|
||||
if (releasedChild == controls) {
|
||||
float direction = -yvel;
|
||||
DrawerState drawerState = DrawerState.COLLAPSED;
|
||||
|
||||
if (direction > 1) {
|
||||
drawerState = DrawerState.FULL_EXPANDED;
|
||||
} else if (direction < -1) {
|
||||
boolean halfExpand = (slideOffset > halfExpandedHeight && !isLandscape());
|
||||
drawerState = halfExpand ? DrawerState.HALF_EXPANDED : DrawerState.COLLAPSED;
|
||||
} else if (!isLandscape()) {
|
||||
if (slideOffset >= (halfExpandedHeight + getMeasuredHeight()) / 2) {
|
||||
drawerState = DrawerState.FULL_EXPANDED;
|
||||
} else if (slideOffset >= halfExpandedHeight / 2) {
|
||||
drawerState = DrawerState.HALF_EXPANDED;
|
||||
}
|
||||
}
|
||||
|
||||
setDrawerState(drawerState);
|
||||
int slideOffset = getTargetSlideOffset();
|
||||
dragHelper.captureChildView(coverView, 0);
|
||||
dragHelper.settleCapturedViewAt(coverView.getLeft(), computeCoverTopPosition(slideOffset));
|
||||
dragHelper.captureChildView(cameraView, 0);
|
||||
dragHelper.settleCapturedViewAt(cameraView.getLeft(), computeCameraTopPosition(slideOffset));
|
||||
ViewCompat.postInvalidateOnAnimation(QuickAttachmentDrawer.this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewVerticalDragRange(View child) {
|
||||
return getMeasuredHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clampViewPositionVertical(View child, int top, int dy) {
|
||||
return top;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent event) {
|
||||
final int action = MotionEventCompat.getActionMasked(event);
|
||||
|
||||
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
|
||||
dragHelper.cancel();
|
||||
return false;
|
||||
}
|
||||
|
||||
final float x = event.getX();
|
||||
final float y = event.getY();
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
initialMotionX = x;
|
||||
initialMotionY = y;
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
final float adx = Math.abs(x - initialMotionX);
|
||||
final float ady = Math.abs(y - initialMotionY);
|
||||
final int dragSlop = dragHelper.getTouchSlop();
|
||||
|
||||
if (adx > dragSlop && ady < dragSlop) {
|
||||
return super.onInterceptTouchEvent(event);
|
||||
}
|
||||
|
||||
if ((ady > dragSlop && adx > ady) || !isDragViewUnder((int) initialMotionX, (int) initialMotionY)) {
|
||||
dragHelper.cancel();
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return dragHelper.shouldInterceptTouchEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(@NonNull MotionEvent event) {
|
||||
dragHelper.processTouchEvent(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
// NOTE: Android Studio bug misreports error, squashing the warning.
|
||||
// https://code.google.com/p/android/issues/detail?id=175977
|
||||
@SuppressWarnings("ResourceType")
|
||||
private boolean isDragViewUnder(int x, int y) {
|
||||
int[] viewLocation = new int[2];
|
||||
cameraView.getLocationOnScreen(viewLocation);
|
||||
int[] parentLocation = new int[2];
|
||||
this.getLocationOnScreen(parentLocation);
|
||||
int screenX = parentLocation[0] + x;
|
||||
int screenY = parentLocation[1] + y;
|
||||
return screenX >= viewLocation[0] && screenX < viewLocation[0] + cameraView.getWidth() &&
|
||||
screenY >= viewLocation[1] && screenY < viewLocation[1] + cameraView.getHeight();
|
||||
}
|
||||
|
||||
private int computeCameraTopPosition(int slideOffset) {
|
||||
final int baseCameraTop = (cameraView.getMeasuredHeight() - halfExpandedHeight) / 2;
|
||||
final int baseOffset = getMeasuredHeight() - slideOffset - baseCameraTop;
|
||||
final float slop = Util.clamp((float)(slideOffset - halfExpandedHeight) / (getMeasuredHeight() - halfExpandedHeight),
|
||||
0f,
|
||||
1f);
|
||||
return baseOffset + (int)(slop * baseCameraTop);
|
||||
}
|
||||
|
||||
private int computeCoverTopPosition(int slideOffset) {
|
||||
return getMeasuredHeight() - getPaddingBottom() - slideOffset - getCoverView().getMeasuredHeight();
|
||||
}
|
||||
|
||||
private void slideTo(int slideOffset, boolean forceInstant) {
|
||||
if (animator != null) {
|
||||
animator.cancel();
|
||||
animator = null;
|
||||
}
|
||||
|
||||
if (!forceInstant) {
|
||||
animator = ObjectAnimator.ofInt(this, "slideOffset", this.slideOffset, slideOffset);
|
||||
animator.setInterpolator(new FastOutSlowInInterpolator());
|
||||
animator.setDuration(400);
|
||||
animator.start();
|
||||
ViewCompat.postInvalidateOnAnimation(this);
|
||||
} else {
|
||||
this.slideOffset = slideOffset;
|
||||
requestLayout();
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void onPause() {
|
||||
paused = true;
|
||||
cameraView.onPause();
|
||||
}
|
||||
|
||||
public void onResume() {
|
||||
paused = false;
|
||||
if (drawerState.isVisible()) cameraView.onResume();
|
||||
}
|
||||
|
||||
public enum DrawerState {
|
||||
COLLAPSED, HALF_EXPANDED, FULL_EXPANDED;
|
||||
|
||||
public boolean isVisible() {
|
||||
return this != COLLAPSED;
|
||||
}
|
||||
}
|
||||
|
||||
private class ShutterClickListener implements OnClickListener {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
boolean crop = drawerState != DrawerState.FULL_EXPANDED;
|
||||
int imageHeight = crop ? getContainer().getKeyboardHeight() : cameraView.getMeasuredHeight();
|
||||
Rect previewRect = new Rect(0, 0, cameraView.getMeasuredWidth(), imageHeight);
|
||||
cameraView.takePicture(previewRect);
|
||||
}
|
||||
}
|
||||
|
||||
private class CameraFlipClickListener implements OnClickListener {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
cameraView.flipCamera();
|
||||
swapCameraButton.setImageResource(cameraView.isRearCamera() ? R.drawable.quick_camera_front
|
||||
: R.drawable.quick_camera_rear);
|
||||
}
|
||||
}
|
||||
|
||||
private class FullscreenClickListener implements OnClickListener {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (drawerState != DrawerState.FULL_EXPANDED) {
|
||||
setDrawerStateAndUpdate(DrawerState.FULL_EXPANDED);
|
||||
} else if (isLandscape()) {
|
||||
setDrawerStateAndUpdate(DrawerState.COLLAPSED);
|
||||
} else {
|
||||
setDrawerStateAndUpdate(DrawerState.HALF_EXPANDED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.util;
|
|||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.hardware.Camera.CameraInfo;
|
||||
import android.net.Uri;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.ContactsContract;
|
||||
|
@ -51,8 +50,6 @@ public class Prefs {
|
|||
public static final String NOTIFICATION_PRIVACY_PREF = "pref_notification_privacy";
|
||||
public static final String NOTIFICATION_PRIORITY_PREF = "pref_notification_priority";
|
||||
|
||||
public static final String BUILTIN_CAMERA_PREF = "pref_builtin_camera";
|
||||
public static final String DIRECT_CAPTURE_CAMERA_ID = "pref_direct_capture_camera_id";
|
||||
private static final String PROFILE_AVATAR_ID_PREF = "pref_profile_avatar_id";
|
||||
public static final String INCOGNITO_KEYBORAD_PREF = "pref_incognito_keyboard";
|
||||
|
||||
|
@ -105,15 +102,6 @@ public class Prefs {
|
|||
return Integer.valueOf(getStringPreference(context, NOTIFICATION_PRIORITY_PREF, String.valueOf(NotificationCompat.PRIORITY_HIGH)));
|
||||
}
|
||||
|
||||
public static void setDirectCaptureCameraId(Context context, int value) {
|
||||
setIntegerPreference(context, DIRECT_CAPTURE_CAMERA_ID, value);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static int getDirectCaptureCameraId(Context context) {
|
||||
return getIntegerPreference(context, DIRECT_CAPTURE_CAMERA_ID, CameraInfo.CAMERA_FACING_FRONT);
|
||||
}
|
||||
|
||||
public static NotificationPrivacyPreference getNotificationPrivacy(Context context) {
|
||||
return new NotificationPrivacyPreference(getStringPreference(context, NOTIFICATION_PRIVACY_PREF, "all"));
|
||||
}
|
||||
|
@ -270,10 +258,6 @@ public class Prefs {
|
|||
setStringPreference(context, BACKGROUND_PREF+accountId, path);
|
||||
}
|
||||
|
||||
public static boolean isBuiltInCameraPreferred(Context context) {
|
||||
return getBooleanPreference(context, BUILTIN_CAMERA_PREF, false);
|
||||
}
|
||||
|
||||
public static boolean getAlwaysLoadRemoteContent(Context context) {
|
||||
return getBooleanPreference(context, Prefs.ALWAYS_LOAD_REMOTE_CONTENT,
|
||||
Prefs.ALWAYS_LOAD_REMOTE_CONTENT_DEFAULT);
|
||||
|
|
|
@ -15,12 +15,6 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer
|
||||
android:id="@+id/quick_attachment_drawer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout android:id="@+id/conversation_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -64,6 +58,5 @@
|
|||
/>
|
||||
|
||||
</LinearLayout>
|
||||
</org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer>
|
||||
</org.thoughtcrime.securesms.components.InputAwareLayout>
|
||||
</RelativeLayout>
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<org.thoughtcrime.securesms.components.camera.CameraView
|
||||
android:id="@+id/quick_camera"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="invisible" />
|
||||
|
||||
</merge>
|
|
@ -742,7 +742,9 @@
|
|||
<string name="pref_manage_keys">Manage Keys</string>
|
||||
<string name="pref_use_system_emoji">Use System Emoji</string>
|
||||
<string name="pref_use_system_emoji_explain">Turn off Delta Chat\'s built-in emoji support</string>
|
||||
<!-- deprecated -->
|
||||
<string name="pref_use_inapp_camera">Use In-App Camera</string>
|
||||
<!-- deprecated -->
|
||||
<string name="pref_use_inapp_camera_explain">In-app camera usually has less features than the system camera</string>
|
||||
<string name="pref_show_system_contacts">Read System Address Book</string>
|
||||
<string name="pref_show_system_contacts_explain">Offer to create chats with contacts from the address book. Some providers need end-to-end encryption setup first.</string>
|
||||
|
|
|
@ -62,12 +62,6 @@
|
|||
android:summary="@string/pref_incognito_keyboard_explain"
|
||||
android:title="@string/pref_incognito_keyboard" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="pref_builtin_camera"
|
||||
android:title="@string/pref_use_inapp_camera"
|
||||
android:summary="@string/pref_use_inapp_camera_explain"/>
|
||||
|
||||
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="pref_show_system_contacts"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue