mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-23 18:15:22 +00:00
parent
47b21707be
commit
1a7ab6346f
@ -11,7 +11,7 @@
|
||||
android:label="Access to TextSecure Secrets"
|
||||
android:protectionLevel="signature" />
|
||||
|
||||
<!--<uses-feature android:name="android.hardware.camera" android:required="false" />-->
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||
<uses-permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"/>
|
||||
<uses-permission android:name="android.permission.READ_PROFILE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_PROFILE"/>
|
||||
@ -34,12 +34,12 @@
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_CALL_LOG" />
|
||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
||||
<!--<uses-permission android:name="android.permission.CAMERA" />-->
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
|
||||
|
||||
<!--<!– For sending location tiles in the future –>-->
|
||||
<!--<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>-->
|
||||
<!--<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>-->
|
||||
<!-- For sending location tiles in the future -->
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
|
||||
<!-- So we can add a TextSecure 'Account' -->
|
||||
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
||||
@ -47,34 +47,34 @@
|
||||
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
|
||||
<uses-permission android:name="android.permission.USE_CREDENTIALS"/>
|
||||
|
||||
<!--<!– For conversation 'shortcuts' on the desktop –>-->
|
||||
<!--<uses-permission android:name="android.permission.INSTALL_SHORTCUT"/>-->
|
||||
<!-- For conversation 'shortcuts' on the desktop -->
|
||||
<uses-permission android:name="android.permission.INSTALL_SHORTCUT"/>
|
||||
|
||||
<!--<!– For sending/receiving events –>-->
|
||||
<!--<uses-permission android:name="android.permission.WRITE_CALENDAR"/>-->
|
||||
<!--<uses-permission android:name="android.permission.READ_CALENDAR"/>-->
|
||||
<!-- For sending/receiving events -->
|
||||
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>
|
||||
<uses-permission android:name="android.permission.READ_CALENDAR"/>
|
||||
|
||||
<!--<!– For fixing MMS –>-->
|
||||
<!--<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>-->
|
||||
<!--<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>-->
|
||||
<!-- For fixing MMS -->
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
|
||||
|
||||
<!--<!– Set image as wallpaper –>-->
|
||||
<!--<uses-permission android:name="android.permission.SET_WALLPAPER"/>-->
|
||||
<!-- -->
|
||||
<!--<!– Permissions from RedPhone –>-->
|
||||
<!--<uses-permission android:name="android.permission.RECORD_AUDIO" />-->
|
||||
<!--<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />-->
|
||||
<!--<uses-permission android:name="android.permission.BLUETOOTH" />-->
|
||||
<!--<uses-permission android:name="android.permission.BROADCAST_STICKY" />-->
|
||||
<!--<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />-->
|
||||
<!--<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />-->
|
||||
<!--<uses-permission android:name="android.permission.CALL_PHONE" />-->
|
||||
<!--<uses-permission android:name="android.permission.CALL_PRIVILEGED" />-->
|
||||
<!--<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />-->
|
||||
<!--<uses-permission android:name="android.permission.RAISED_THREAD_PRIORITY" />-->
|
||||
<!--<uses-permission android:name="android.permission.READ_CALL_STATE"/>-->
|
||||
<!--<uses-permission android:name="android.permission.READ_LOGS"/>-->
|
||||
<!--<uses-permission android:name="android.permission.WRITE_CALL_LOG"/>-->
|
||||
<!-- Set image as wallpaper -->
|
||||
<uses-permission android:name="android.permission.SET_WALLPAPER"/>
|
||||
|
||||
<!-- Permissions from RedPhone -->
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
|
||||
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
|
||||
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
|
||||
<uses-permission android:name="android.permission.CALL_PHONE" />
|
||||
<uses-permission android:name="android.permission.CALL_PRIVILEGED" />
|
||||
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
|
||||
<uses-permission android:name="android.permission.RAISED_THREAD_PRIORITY" />
|
||||
<uses-permission android:name="android.permission.READ_CALL_STATE"/>
|
||||
<uses-permission android:name="android.permission.READ_LOGS"/>
|
||||
<uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
|
||||
|
||||
<permission android:name="org.thoughtcrime.securesms.permission.C2D_MESSAGE"
|
||||
android:protectionLevel="signature" />
|
||||
|
@ -109,7 +109,6 @@
|
||||
android:src="?quick_camera_icon"
|
||||
android:background="@drawable/touch_highlight_background"
|
||||
android:contentDescription="@string/conversation_activity__quick_attachment_drawer_toggle_description"
|
||||
android:visibility="gone"
|
||||
android:padding="10dp" />
|
||||
</LinearLayout>
|
||||
|
||||
|
@ -5,6 +5,6 @@
|
||||
android:id="@+id/quick_camera"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone" />
|
||||
android:visibility="invisible" />
|
||||
|
||||
</merge>
|
@ -59,6 +59,7 @@ import android.widget.TextView.OnEditorActionListener;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.afollestad.materialdialogs.AlertDialogWrapper;
|
||||
import com.commonsware.cwac.camera.CameraHost.FailureReason;
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.thoughtcrime.securesms.TransportOptions.OnTransportChangedListener;
|
||||
@ -96,6 +97,7 @@ import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.providers.CaptureProvider;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||
@ -815,7 +817,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
ComposeKeyPressedListener composeKeyPressedListener = new ComposeKeyPressedListener();
|
||||
|
||||
attachButton.setOnClickListener(new AttachButtonListener());
|
||||
quickAttachmentToggle.setEnabled(false);
|
||||
sendButton.setOnClickListener(sendButtonListener);
|
||||
sendButton.setEnabled(true);
|
||||
sendButton.addOnTransportChangedListener(new OnTransportChangedListener() {
|
||||
@ -978,7 +979,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
case AttachmentTypeSelectorAdapter.ADD_CONTACT_INFO:
|
||||
AttachmentManager.selectContactInfo(this, PICK_CONTACT_INFO); break;
|
||||
case AttachmentTypeSelectorAdapter.TAKE_PHOTO:
|
||||
attachmentManager.capturePhoto(this, TAKE_PHOTO); break;
|
||||
attachmentManager.capturePhoto(this, recipients, TAKE_PHOTO); break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1332,11 +1333,19 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onImageCapture(@NonNull final Bitmap bitmap) {
|
||||
attachmentManager.setCaptureImage(masterSecret, bitmap);
|
||||
public void onImageCapture(@NonNull final byte[] imageBytes) {
|
||||
attachmentManager.setCaptureUri(CaptureProvider.getInstance(this).create(masterSecret, recipients, imageBytes));
|
||||
addAttachmentImage(masterSecret, attachmentManager.getCaptureUri());
|
||||
quickAttachmentDrawer.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCameraFail(FailureReason reason) {
|
||||
Toast.makeText(this, R.string.quick_camera_unavailable, Toast.LENGTH_SHORT).show();
|
||||
quickAttachmentDrawer.close();
|
||||
quickAttachmentToggle.disable();
|
||||
}
|
||||
|
||||
// Listeners
|
||||
|
||||
private class AttachmentTypeListener implements DialogInterface.OnClickListener {
|
||||
|
@ -19,7 +19,6 @@ import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.graphics.Color;
|
||||
import android.hardware.Camera;
|
||||
import android.hardware.Camera.PreviewCallback;
|
||||
import android.os.Build;
|
||||
@ -30,7 +29,6 @@ import android.util.Log;
|
||||
import android.view.OrientationEventListener;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -91,7 +89,7 @@ public class CameraView extends FrameLayout {
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
|
||||
public void onResume() {
|
||||
Log.w(TAG, "onResume()");
|
||||
Log.w(TAG, "onResume() queued");
|
||||
final CameraHost host = getHost();
|
||||
submitTask(new SerializedAsyncTask<FailureReason>() {
|
||||
@Override protected FailureReason onRunBackground() {
|
||||
@ -110,11 +108,11 @@ public class CameraView extends FrameLayout {
|
||||
}
|
||||
|
||||
@Override protected void onPostMain(FailureReason result) {
|
||||
cameraReady = true;
|
||||
if (result != null) {
|
||||
host.onCameraFail(result);
|
||||
return;
|
||||
}
|
||||
cameraReady = true;
|
||||
if (getActivity().getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
|
||||
onOrientationChange.enable();
|
||||
}
|
||||
@ -126,12 +124,13 @@ public class CameraView extends FrameLayout {
|
||||
initPreview();
|
||||
requestLayout();
|
||||
invalidate();
|
||||
Log.w(TAG, "onResume() completed");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void onPause() {
|
||||
Log.w(TAG, "onPause()");
|
||||
Log.w(TAG, "onPause() queued");
|
||||
submitTask(new SerializedAsyncTask<Void>() {
|
||||
@Override protected void onPreMain() {
|
||||
cameraReady = false;
|
||||
@ -151,6 +150,7 @@ public class CameraView extends FrameLayout {
|
||||
outputOrientation = -1;
|
||||
cameraId = -1;
|
||||
lastPictureOrientation = -1;
|
||||
Log.w(TAG, "onPause() completed");
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -255,6 +255,7 @@ public class CameraView extends FrameLayout {
|
||||
}
|
||||
|
||||
void previewCreated() {
|
||||
Log.w(TAG, "previewCreated() queued");
|
||||
final CameraHost host = getHost();
|
||||
submitTask(new PostInitializationTask<Void>() {
|
||||
@Override protected void onPostMain(Void avoid) {
|
||||
@ -265,6 +266,7 @@ public class CameraView extends FrameLayout {
|
||||
} catch (IOException e) {
|
||||
host.handleException(e);
|
||||
}
|
||||
Log.w(TAG, "previewCreated() completed");
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -277,11 +279,6 @@ public class CameraView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
void previewReset() {
|
||||
previewStopped();
|
||||
initPreview();
|
||||
}
|
||||
|
||||
private void previewStopped() {
|
||||
if (inPreview) {
|
||||
stopPreview();
|
||||
@ -290,6 +287,7 @@ public class CameraView extends FrameLayout {
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
|
||||
public void initPreview() {
|
||||
Log.w(TAG, "initPreview() queued");
|
||||
submitTask(new PostInitializationTask<Void>() {
|
||||
@Override protected void onPostMain(Void avoid) {
|
||||
if (camera != null && cameraReady) {
|
||||
@ -305,20 +303,19 @@ public class CameraView extends FrameLayout {
|
||||
startPreview();
|
||||
requestLayout();
|
||||
invalidate();
|
||||
Log.w(TAG, "initPreview() completed");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void startPreview() {
|
||||
Log.w(TAG, "startPreview()");
|
||||
camera.startPreview();
|
||||
inPreview = true;
|
||||
getHost().autoFocusAvailable();
|
||||
}
|
||||
|
||||
private void stopPreview() {
|
||||
Log.w(TAG, "stopPreview()");
|
||||
camera.startPreview();
|
||||
inPreview = false;
|
||||
getHost().autoFocusUnavailable();
|
||||
@ -453,6 +450,7 @@ public class CameraView extends FrameLayout {
|
||||
@Override public void onAdded() {}
|
||||
|
||||
@Override public final void onRun() {
|
||||
try {
|
||||
onWait();
|
||||
runOnMainSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
@ -467,6 +465,9 @@ public class CameraView extends FrameLayout {
|
||||
onPostMain(result);
|
||||
}
|
||||
});
|
||||
} catch (PreconditionsNotMetException e) {
|
||||
Log.w(TAG, "skipping task, preconditions not met in onWait()");
|
||||
}
|
||||
}
|
||||
|
||||
@Override public boolean onShouldRetry(Exception e) {
|
||||
@ -493,19 +494,26 @@ public class CameraView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
protected void onWait() {}
|
||||
protected void onWait() throws PreconditionsNotMetException {}
|
||||
protected void onPreMain() {}
|
||||
protected Result onRunBackground() { return null; }
|
||||
protected void onPostMain(Result result) {}
|
||||
}
|
||||
|
||||
private abstract class PostInitializationTask<Result> extends SerializedAsyncTask<Result> {
|
||||
@Override protected void onWait() {
|
||||
@Override protected void onWait() throws PreconditionsNotMetException {
|
||||
synchronized (CameraView.this) {
|
||||
if (!cameraReady) {
|
||||
throw new PreconditionsNotMetException();
|
||||
}
|
||||
while (camera == null || previewSize == null || !previewStrategy.isReady()) {
|
||||
Log.w(TAG, String.format("waiting. camera? %s previewSize? %s prevewStrategy? %s",
|
||||
camera != null, previewSize != null, previewStrategy.isReady()));
|
||||
Util.wait(CameraView.this, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class PreconditionsNotMetException extends Exception {}
|
||||
}
|
||||
|
@ -52,6 +52,7 @@ public class QuickAttachmentDrawer extends ViewGroup {
|
||||
private float halfExpandedAnchorPoint = COLLAPSED_ANCHOR_POINT;
|
||||
private boolean halfModeUnsupported = VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH;
|
||||
private Rect drawChildrenRect = new Rect();
|
||||
private boolean paused = false;
|
||||
|
||||
public QuickAttachmentDrawer(Context context) {
|
||||
this(context, null);
|
||||
@ -128,7 +129,7 @@ public class QuickAttachmentDrawer extends ViewGroup {
|
||||
}
|
||||
shutterButton.setOnClickListener(new ShutterClickListener());
|
||||
fullScreenButton.setOnClickListener(new FullscreenClickListener());
|
||||
controls.setVisibility(GONE);
|
||||
controls.setVisibility(INVISIBLE);
|
||||
addView(controls, controlsIndex > -1 ? controlsIndex : indexOfChild(quickCamera) + 1);
|
||||
}
|
||||
|
||||
@ -274,10 +275,10 @@ public class QuickAttachmentDrawer extends ViewGroup {
|
||||
}
|
||||
|
||||
if (slideOffset == COLLAPSED_ANCHOR_POINT && quickCamera.isStarted()) {
|
||||
controls.setVisibility(GONE);
|
||||
quickCamera.setVisibility(GONE);
|
||||
quickCamera.onPause();
|
||||
} else if (slideOffset != COLLAPSED_ANCHOR_POINT && !quickCamera.isStarted()) {
|
||||
controls.setVisibility(INVISIBLE);
|
||||
quickCamera.setVisibility(INVISIBLE);
|
||||
} else if (slideOffset != COLLAPSED_ANCHOR_POINT && !quickCamera.isStarted() & !paused) {
|
||||
controls.setVisibility(VISIBLE);
|
||||
quickCamera.setVisibility(VISIBLE);
|
||||
quickCamera.onResume();
|
||||
@ -507,10 +508,12 @@ public class QuickAttachmentDrawer extends ViewGroup {
|
||||
}
|
||||
|
||||
public void onPause() {
|
||||
paused = true;
|
||||
quickCamera.onPause();
|
||||
}
|
||||
|
||||
public void onResume() {
|
||||
paused = false;
|
||||
if (drawerState.isVisible()) quickCamera.onResume();
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.components.camera;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Rect;
|
||||
import android.hardware.Camera;
|
||||
import android.hardware.Camera.CameraInfo;
|
||||
@ -13,11 +12,10 @@ import android.os.Build.VERSION_CODES;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.commonsware.cwac.camera.CameraHost.FailureReason;
|
||||
import com.commonsware.cwac.camera.SimpleCameraHost;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -79,28 +77,32 @@ import java.util.List;
|
||||
@Override
|
||||
public void onPreviewFrame(byte[] data, final Camera camera) {
|
||||
final int rotation = getCameraPictureOrientation();
|
||||
final Size previewSize = cameraParameters.getPreviewSize();
|
||||
final Rect croppingRect = getCroppedRect(previewSize, previewRect, rotation);
|
||||
|
||||
new AsyncTask<byte[], Void, Bitmap>() {
|
||||
Log.w(TAG, "previewSize: " + previewSize.width + "x" + previewSize.height);
|
||||
Log.w(TAG, "croppingRect: " + croppingRect.toString());
|
||||
Log.w(TAG, "rotation: " + rotation);
|
||||
new AsyncTask<byte[], Void, byte[]>() {
|
||||
@Override
|
||||
protected Bitmap doInBackground(byte[]... params) {
|
||||
protected byte[] doInBackground(byte[]... params) {
|
||||
byte[] data = params[0];
|
||||
try {
|
||||
Size previewSize = cameraParameters.getPreviewSize();
|
||||
|
||||
return BitmapUtil.createFromNV21(data,
|
||||
previewSize.width,
|
||||
previewSize.height,
|
||||
rotation,
|
||||
getCroppedRect(previewSize, previewRect, rotation));
|
||||
croppingRect);
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Bitmap bitmap) {
|
||||
protected void onPostExecute(byte[] imageBytes) {
|
||||
capturing = false;
|
||||
if (bitmap != null && listener != null) listener.onImageCapture(bitmap);
|
||||
if (imageBytes != null && listener != null) listener.onImageCapture(imageBytes);
|
||||
}
|
||||
}.execute(data);
|
||||
}
|
||||
@ -111,10 +113,7 @@ import java.util.List;
|
||||
final int previewWidth = cameraPreviewSize.width;
|
||||
final int previewHeight = cameraPreviewSize.height;
|
||||
|
||||
if (rotation % 180 > 0) {
|
||||
//noinspection SuspiciousNameCombination
|
||||
visibleRect.set(visibleRect.top, visibleRect.left, visibleRect.bottom, visibleRect.right);
|
||||
}
|
||||
if (rotation % 180 > 0) rotateRect(visibleRect);
|
||||
|
||||
float scale = (float) previewWidth / visibleRect.width();
|
||||
if (visibleRect.height() * scale > previewHeight) {
|
||||
@ -128,9 +127,16 @@ import java.util.List;
|
||||
(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);
|
||||
}
|
||||
|
||||
public void setQuickCameraListener(QuickCameraListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
@ -150,7 +156,8 @@ import java.util.List;
|
||||
}
|
||||
|
||||
public interface QuickCameraListener {
|
||||
void onImageCapture(@NonNull final Bitmap bitmap);
|
||||
void onImageCapture(@NonNull final byte[] imageBytes);
|
||||
void onCameraFail(FailureReason reason);
|
||||
}
|
||||
|
||||
private class QuickCameraHost extends SimpleCameraHost {
|
||||
@ -186,7 +193,7 @@ import java.util.List;
|
||||
@Override
|
||||
public void onCameraFail(FailureReason reason) {
|
||||
super.onCameraFail(reason);
|
||||
Toast.makeText(getContext(), R.string.quick_camera_unavailable, Toast.LENGTH_SHORT).show();
|
||||
if (listener != null) listener.onCameraFail(reason);
|
||||
}
|
||||
}
|
||||
}
|
@ -20,7 +20,6 @@ import android.app.Activity;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.ContactsContract;
|
||||
@ -30,9 +29,6 @@ import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.animation.AlphaAnimation;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.ScaleAnimation;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Toast;
|
||||
|
||||
@ -40,9 +36,9 @@ import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.ThumbnailView;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.providers.CaptureProvider;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class AttachmentManager {
|
||||
@ -94,8 +90,7 @@ public class AttachmentManager {
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
// if (captureUri != null) CaptureProvider.getInstance(context).delete(captureUri);
|
||||
if (captureUri != null) new File(captureUri.getPath()).delete();
|
||||
if (captureUri != null) CaptureProvider.getInstance(context).delete(captureUri);
|
||||
captureUri = null;
|
||||
}
|
||||
|
||||
@ -127,19 +122,11 @@ public class AttachmentManager {
|
||||
return attachmentView.getVisibility() == View.VISIBLE;
|
||||
}
|
||||
|
||||
|
||||
public SlideDeck getSlideDeck() {
|
||||
return slideDeck;
|
||||
}
|
||||
|
||||
public void setCaptureImage(MasterSecret masterSecret, Bitmap bitmap) {
|
||||
try {
|
||||
captureUri = CaptureProvider.getInstance(context).create(masterSecret, bitmap);
|
||||
setImage(masterSecret, captureUri);
|
||||
} catch (IOException | BitmapDecodingException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void selectVideo(Activity activity, int requestCode) {
|
||||
selectMediaType(activity, "video/*", requestCode);
|
||||
}
|
||||
@ -161,12 +148,16 @@ public class AttachmentManager {
|
||||
return captureUri;
|
||||
}
|
||||
|
||||
public void capturePhoto(Activity activity, int requestCode) {
|
||||
|
||||
public void setCaptureUri(Uri captureUri) {
|
||||
this.captureUri = captureUri;
|
||||
}
|
||||
|
||||
public void capturePhoto(Activity activity, Recipients recipients, int requestCode) {
|
||||
try {
|
||||
Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||
if (captureIntent.resolveActivity(activity.getPackageManager()) != null) {
|
||||
File captureFile = File.createTempFile(String.valueOf(System.currentTimeMillis()), ".jpg", activity.getExternalFilesDir(null));
|
||||
captureUri = Uri.fromFile(captureFile);
|
||||
captureUri = CaptureProvider.getInstance(context).createForExternal(recipients);
|
||||
captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, captureUri);
|
||||
activity.startActivityForResult(captureIntent, requestCode);
|
||||
}
|
||||
|
@ -2,27 +2,39 @@ package org.thoughtcrime.securesms.providers;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.CompressFormat;
|
||||
import android.content.UriMatcher;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.util.SparseArrayCompat;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.crypto.EncryptingPartOutputStream;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class CaptureProvider {
|
||||
private static final String TAG = CaptureProvider.class.getSimpleName();
|
||||
private static final String URI_STRING = "content://org.thoughtcrime.securesms/capture";
|
||||
public static final Uri CONTENT_URI = Uri.parse(URI_STRING);
|
||||
public static final String AUTHORITY = "org.thoughtcrime.securesms";
|
||||
public static final String EXPECTED_PATH = "capture/#";
|
||||
public static final String EXPECTED_PATH = "capture/*/#";
|
||||
private static final int MATCH = 1;
|
||||
public static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH) {{
|
||||
addURI(AUTHORITY, EXPECTED_PATH, MATCH);
|
||||
}};
|
||||
|
||||
private static volatile CaptureProvider instance;
|
||||
|
||||
public static CaptureProvider getInstance(Context context) {
|
||||
if (instance == null) {
|
||||
synchronized (CaptureProvider.class) {
|
||||
@ -35,28 +47,68 @@ public class CaptureProvider {
|
||||
}
|
||||
|
||||
private final Context context;
|
||||
private final SparseArrayCompat<byte[]> cache = new SparseArrayCompat<>();
|
||||
|
||||
private CaptureProvider(Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
}
|
||||
|
||||
public Uri create(MasterSecret masterSecret, Bitmap bitmap) throws IOException {
|
||||
long id = System.currentTimeMillis();
|
||||
OutputStream output = new EncryptingPartOutputStream(getFile(id), masterSecret);
|
||||
bitmap.compress(CompressFormat.JPEG, 100, output);
|
||||
output.close();
|
||||
return ContentUris.withAppendedId(CONTENT_URI, id);
|
||||
public Uri create(@NonNull MasterSecret masterSecret,
|
||||
@NonNull Recipients recipients,
|
||||
@NonNull byte[] imageBytes)
|
||||
{
|
||||
final int id = generateId(recipients);
|
||||
cache.put(id, imageBytes);
|
||||
persistToDisk(masterSecret, id, imageBytes);
|
||||
final Uri uniqueUri = Uri.withAppendedPath(CONTENT_URI, String.valueOf(System.currentTimeMillis()));
|
||||
return ContentUris.withAppendedId(uniqueUri, id);
|
||||
}
|
||||
|
||||
public boolean delete(Uri uri) {
|
||||
return getFile(ContentUris.parseId(uri)).delete();
|
||||
private void persistToDisk(final MasterSecret masterSecret, final int id, final byte[] imageBytes) {
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override protected Void doInBackground(Void... params) {
|
||||
try {
|
||||
final OutputStream output = new EncryptingPartOutputStream(getFile(id), masterSecret);
|
||||
Util.copy(new ByteArrayInputStream(imageBytes), output);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override protected void onPostExecute(Void aVoid) {
|
||||
cache.remove(id);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
public Uri createForExternal(@NonNull Recipients recipients) throws IOException {
|
||||
final File externalDir = context.getExternalFilesDir(null);
|
||||
if (externalDir == null) throw new IOException("no external files directory");
|
||||
return Uri.fromFile(new File(externalDir, String.valueOf(generateId(recipients)) + ".jpg"))
|
||||
.buildUpon()
|
||||
.appendQueryParameter("unique", String.valueOf(System.currentTimeMillis()))
|
||||
.build();
|
||||
}
|
||||
|
||||
public boolean delete(@NonNull Uri uri) {
|
||||
switch (uriMatcher.match(uri)) {
|
||||
case MATCH: return getFile(ContentUris.parseId(uri)).delete();
|
||||
default: return new File(uri.getPath()).delete();
|
||||
}
|
||||
}
|
||||
|
||||
public InputStream getStream(MasterSecret masterSecret, long id) throws IOException {
|
||||
return new DecryptingPartInputStream(getFile(id), masterSecret);
|
||||
final byte[] cached = cache.get((int)id);
|
||||
return cached != null ? new ByteArrayInputStream(cached)
|
||||
: new DecryptingPartInputStream(getFile(id), masterSecret);
|
||||
}
|
||||
|
||||
private int generateId(Recipients recipients) {
|
||||
return Math.abs(Arrays.hashCode(recipients.getIds()));
|
||||
}
|
||||
|
||||
private File getFile(long id) {
|
||||
return new File(context.getDir("captures", Context.MODE_PRIVATE), id + ".capture");
|
||||
return new File(context.getDir("captures", Context.MODE_PRIVATE), id + ".jpg");
|
||||
}
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ public class BitmapUtil {
|
||||
|
||||
private static Bitmap createScaledBitmap(InputStream measure, InputStream orientationStream, InputStream data,
|
||||
int maxWidth, int maxHeight, boolean constrainedMemory)
|
||||
throws BitmapDecodingException
|
||||
throws IOException, BitmapDecodingException
|
||||
{
|
||||
Bitmap bitmap = createScaledBitmap(measure, data, maxWidth, maxHeight, constrainedMemory);
|
||||
return fixOrientation(bitmap, orientationStream);
|
||||
@ -202,9 +202,9 @@ public class BitmapUtil {
|
||||
return scaler;
|
||||
}
|
||||
|
||||
private static Bitmap fixOrientation(Bitmap bitmap, InputStream orientationStream) {
|
||||
private static Bitmap fixOrientation(Bitmap bitmap, InputStream orientationStream) throws IOException {
|
||||
final int orientation = Exif.getOrientation(orientationStream);
|
||||
|
||||
orientationStream.close();
|
||||
if (orientation != 0) {
|
||||
return rotateBitmap(bitmap, orientation);
|
||||
} else {
|
||||
@ -272,22 +272,65 @@ public class BitmapUtil {
|
||||
return output;
|
||||
}
|
||||
|
||||
public static Bitmap createFromNV21(@NonNull final byte[] data,
|
||||
public static byte[] createFromNV21(@NonNull final byte[] data,
|
||||
final int width,
|
||||
final int height,
|
||||
final int rotation,
|
||||
int rotation,
|
||||
final Rect croppingRect)
|
||||
throws IOException
|
||||
{
|
||||
YuvImage previewImage = new YuvImage(data, ImageFormat.NV21,
|
||||
width, height, null);
|
||||
byte[] rotated = rotateNV21(data, width, height, rotation);
|
||||
final int rotatedWidth = rotation % 180 > 0 ? height : width;
|
||||
final int rotatedHeight = rotation % 180 > 0 ? width : height;
|
||||
YuvImage previewImage = new YuvImage(rotated, ImageFormat.NV21,
|
||||
rotatedWidth, rotatedHeight, null);
|
||||
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
previewImage.compressToJpeg(croppingRect, 100, outputStream);
|
||||
previewImage.compressToJpeg(croppingRect, 80, outputStream);
|
||||
byte[] bytes = outputStream.toByteArray();
|
||||
outputStream.close();
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
return BitmapUtil.rotateBitmap(BitmapFactory.decodeByteArray(bytes, 0, bytes.length), rotation);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static byte[] rotateNV21(@NonNull final byte[] yuv,
|
||||
final int width,
|
||||
final int height,
|
||||
final int rotation)
|
||||
{
|
||||
if (rotation == 0) return yuv;
|
||||
if (rotation % 90 != 0 || rotation < 0 || rotation > 270) {
|
||||
throw new IllegalArgumentException("0 <= rotation < 360, rotation % 90 == 0");
|
||||
}
|
||||
|
||||
final byte[] output = new byte[yuv.length];
|
||||
final int frameSize = width * height;
|
||||
final boolean swap = rotation % 180 != 0;
|
||||
final boolean xflip = rotation % 270 != 0;
|
||||
final boolean yflip = rotation >= 180;
|
||||
|
||||
for (int j = 0; j < height; j++) {
|
||||
for (int i = 0; i < width; i++) {
|
||||
final int yIn = j * width + i;
|
||||
final int uIn = frameSize + (j >> 1) * width + (i & ~1);
|
||||
final int vIn = uIn + 1;
|
||||
|
||||
final int wOut = swap ? height : width;
|
||||
final int hOut = swap ? width : height;
|
||||
final int iSwapped = swap ? j : i;
|
||||
final int jSwapped = swap ? i : j;
|
||||
final int iOut = xflip ? wOut - iSwapped - 1 : iSwapped;
|
||||
final int jOut = yflip ? hOut - jSwapped - 1 : jSwapped;
|
||||
|
||||
final int yOut = jOut * wOut + iOut;
|
||||
final int uOut = frameSize + (jOut >> 1) * wOut + (iOut & ~1);
|
||||
final int vOut = uOut + 1;
|
||||
|
||||
output[yOut] = (byte)(0xff & yuv[yIn]);
|
||||
output[uOut] = (byte)(0xff & yuv[uIn]);
|
||||
output[vOut] = (byte)(0xff & yuv[vIn]);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
public static Bitmap createFromDrawable(final Drawable drawable, final int width, final int height) {
|
||||
|
Loading…
Reference in New Issue
Block a user