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