diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 19af8e46c5..f6bf731535 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -11,7 +11,7 @@
android:label="Access to TextSecure Secrets"
android:protectionLevel="signature" />
-
+
@@ -34,12 +34,12 @@
-
+
-
-
-
+
+
+
@@ -47,34 +47,34 @@
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/conversation_activity.xml b/res/layout/conversation_activity.xml
index 3e540c2109..6912cfa44d 100644
--- a/res/layout/conversation_activity.xml
+++ b/res/layout/conversation_activity.xml
@@ -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" />
diff --git a/res/layout/quick_attachment_drawer.xml b/res/layout/quick_attachment_drawer.xml
index 717fb1720d..1bb3907592 100644
--- a/res/layout/quick_attachment_drawer.xml
+++ b/res/layout/quick_attachment_drawer.xml
@@ -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" />
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java
index dd164ede53..ecbc5f54dd 100644
--- a/src/org/thoughtcrime/securesms/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationActivity.java
@@ -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 {
diff --git a/src/org/thoughtcrime/securesms/components/camera/CameraView.java b/src/org/thoughtcrime/securesms/components/camera/CameraView.java
index a60f4b75f7..7d2a5e78b6 100644
--- a/src/org/thoughtcrime/securesms/components/camera/CameraView.java
+++ b/src/org/thoughtcrime/securesms/components/camera/CameraView.java
@@ -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() {
@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() {
@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() {
@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() {
@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,20 +450,24 @@ public class CameraView extends FrameLayout {
@Override public void onAdded() {}
@Override public final void onRun() {
- onWait();
- runOnMainSync(new Runnable() {
- @Override public void run() {
- onPreMain();
- }
- });
+ try {
+ onWait();
+ runOnMainSync(new Runnable() {
+ @Override public void run() {
+ onPreMain();
+ }
+ });
- final Result result = onRunBackground();
+ final Result result = onRunBackground();
- runOnMainSync(new Runnable() {
- @Override public void run() {
- onPostMain(result);
- }
- });
+ 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) {
@@ -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 extends SerializedAsyncTask {
- @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 {}
}
diff --git a/src/org/thoughtcrime/securesms/components/camera/QuickAttachmentDrawer.java b/src/org/thoughtcrime/securesms/components/camera/QuickAttachmentDrawer.java
index eb44396473..60c24b3f51 100644
--- a/src/org/thoughtcrime/securesms/components/camera/QuickAttachmentDrawer.java
+++ b/src/org/thoughtcrime/securesms/components/camera/QuickAttachmentDrawer.java
@@ -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();
}
diff --git a/src/org/thoughtcrime/securesms/components/camera/QuickCamera.java b/src/org/thoughtcrime/securesms/components/camera/QuickCamera.java
index 4eba2df59a..1f3e108859 100644
--- a/src/org/thoughtcrime/securesms/components/camera/QuickCamera.java
+++ b/src/org/thoughtcrime/securesms/components/camera/QuickCamera.java
@@ -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;
@@ -78,29 +76,33 @@ import java.util.List;
setOneShotPreviewCallback(new Camera.PreviewCallback() {
@Override
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() {
+ Log.w(TAG, "previewSize: " + previewSize.width + "x" + previewSize.height);
+ Log.w(TAG, "croppingRect: " + croppingRect.toString());
+ Log.w(TAG, "rotation: " + rotation);
+ new AsyncTask() {
@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);
}
}
}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
index 4a8aa5291f..cfe8477515 100644
--- a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
+++ b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
@@ -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);
}
diff --git a/src/org/thoughtcrime/securesms/providers/CaptureProvider.java b/src/org/thoughtcrime/securesms/providers/CaptureProvider.java
index 566e08ef27..685914ae31 100644
--- a/src/org/thoughtcrime/securesms/providers/CaptureProvider.java
+++ b/src/org/thoughtcrime/securesms/providers/CaptureProvider.java
@@ -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 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() {
+ @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");
}
}
diff --git a/src/org/thoughtcrime/securesms/util/BitmapUtil.java b/src/org/thoughtcrime/securesms/util/BitmapUtil.java
index 5582f221af..fef86d5da8 100644
--- a/src/org/thoughtcrime/securesms/util/BitmapUtil.java
+++ b/src/org/thoughtcrime/securesms/util/BitmapUtil.java
@@ -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) {