Compare commits

...

8 Commits

Author SHA1 Message Date
Moxie Marlinspike
c867316e6a Bump version to 3.3.2
// FREEBIE
2015-11-11 08:33:28 -08:00
Jake McGinty
b0137c08cb go back to old CameraView
Fixes #4415
Closes #4484
// FREEBIE
2015-11-10 15:48:50 -08:00
Simeon J Morgan
534421eb57 Update build instructions with new URLs.
Expand Android Studio steps.

Closes #4462
2015-11-10 14:14:25 -08:00
Moxie Marlinspike
b97363e407 Reintroduce telephony requirement
Fixes #4457
// FREEBIE
2015-11-09 09:58:24 -08:00
Moxie Marlinspike
aaf3f70ce5 Bump version to 3.3.1
// FREEBIE
2015-11-06 16:59:50 -08:00
Jake McGinty
5fd5b1e1ed Fix "twitchy fingers" bug in CameraView
better diagnostic information, too.

Fixes #4422
Closes #4427
// FREEBIE
2015-11-06 15:00:29 -08:00
Moxie Marlinspike
56a3c99289 Resolve service deadlock issue
Fixes #4409
// FREEBIE
2015-11-06 11:57:22 -08:00
Moxie Marlinspike
b5b564cfe1 Make sure the number we add canonicalizes to the number we know.
Fixes #4406
// FREEBIE
2015-11-06 10:50:47 -08:00
23 changed files with 815 additions and 484 deletions

View File

@@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.thoughtcrime.securesms"
android:versionCode="163"
android:versionName="3.3.0">
android:versionCode="165"
android:versionName="3.3.2">
<uses-sdk tools:overrideLibrary="com.amulyakhare.textdrawable,com.astuetz.pagerslidingtabstrip,pl.tajchert.waitingdots,com.h6ah4i.android.multiselectlistpreferencecompat"/>
@@ -19,7 +19,6 @@
<uses-feature android:name="android.hardware.microphone" android:required="false"/>
<uses-feature android:name="android.hardware.wifi" android:required="false"/>
<uses-feature android:name="android.hardware.portrait" android:required="false"/>
<uses-feature android:name="android.hardware.telephony" android:required="false"/>
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
<uses-permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"/>

View File

@@ -1,22 +1,22 @@
Building TextSecure
=====================
Building Signal
===============
Basics
------
TextSecure uses [Gradle](http://gradle.org) to build the project and to maintain
Signal uses [Gradle](http://gradle.org) to build the project and to maintain
dependencies.
Building TextSecure
-------------------
Building Signal
---------------
The following steps should help you (re)build TextSecure from the command line.
The following steps should help you (re)build Signal from the command line.
1. Checkout the source somewhere on your filesystem with
1. Checkout the Signal-Android project source with the command:
git clone https://github.com/WhisperSystems/TextSecure.git
git clone https://github.com/WhisperSystems/Signal-Android.git
2. Make sure you have the [Android SDK](https://developer.android.com/sdk/index.html) installed somewhere on your system.
2. Make sure you have the [Android SDK](https://developer.android.com/sdk/index.html) installed.
3. Ensure that the following packages are installed from the Android SDK manager:
* Android SDK Build Tools
* SDK Platform
@@ -38,8 +38,8 @@ Source assets tend to be large binary blobs, which are best stored outside of gi
Sample command for generating our audio placeholder image:
```bash
pngs_from_svg.py ic_audio.svg /path/to/TextSecure/res/ 150 --color #000 --opacity 0.54 --suffix _light
pngs_from_svg.py ic_audio.svg /path/to/TextSecure/res/ 150 --color #fff --opacity 1.00 --suffix _light
pngs_from_svg.py ic_audio.svg /path/to/Signal/res/ 150 --color #000 --opacity 0.54 --suffix _light
pngs_from_svg.py ic_audio.svg /path/to/Signal/res/ 150 --color #fff --opacity 1.00 --suffix _light
```
Setting up a development environment
@@ -48,13 +48,14 @@ Setting up a development environment
[Android Studio](https://developer.android.com/sdk/installing/studio.html) is the recommended development environment.
1. Install Android Studio.
2. Make sure the "Android Support Repository" is installed in the Android Studio SDK.
3. Make sure the latest "Android SDK build-tools" is installed in the Android Studio SDK.
4. Create a new Android Studio project. from the Quickstart pannel (use File > Close Project to see it), choose "Checkout from Version Control" then "git".
5. Paste the URL for the TextSecure project when prompted (https://github.com/WhisperSystems/TextSecure.git).
6. Android studio should detect the presence of a project file and ask you whether to open it. Click "yes".
7. Default config options should be good enough.
8. Project initialisation and build should proceed.
2. Open Android Studio. On a new installation, the Quickstart panel will appear. If you have open projects, close them using "File > Close Project" to see the Quickstart panel.
3. From the Quickstart panel, choose "Configure" then "SDK Manager".
4. In the SDK Tools tab of the SDK Manager, make sure that the "Android Support Repository" is installed, and that the latest "Android SDK build-tools" are installed. Click "OK" to return to the Quickstart panel.
5. From the Quickstart panel, choose "Checkout from Version Control" then "git".
6. Paste the URL for the Signal-Android project when prompted (https://github.com/WhisperSystems/Signal-Android.git).
7. Android studio should detect the presence of a project file and ask you whether to open it. Click "yes".
9. Default config options should be good enough.
9. Project initialisation and build should proceed.
Contributing code
-----------------
@@ -67,3 +68,4 @@ Mailing list
Development discussion happens on the whispersystems mailing list.
[To join](https://lists.riseup.net/www/info/whispersystems)
Send emails to whispersystems@lists.riseup.net

View File

@@ -32,6 +32,9 @@ repositories {
maven { // material-dialogs
url "https://jitpack.io"
}
maven { // cwac-camera
url 'https://repo.commonsware.com.s3.amazonaws.com'
}
jcenter()
mavenLocal()
}
@@ -72,6 +75,7 @@ dependencies {
exclude group: 'com.android.support', module: 'support-v4'
}
compile 'com.madgag.spongycastle:prov:1.51.0.0'
compile 'com.commonsware.cwac:camera:0.6.12'
provided 'com.squareup.dagger:dagger-compiler:1.2.2'
compile 'org.whispersystems:jobmanager:1.0.2'
@@ -124,6 +128,7 @@ dependencyVerification {
'com.squareup.dagger:dagger:789aca24537022e49f91fc6444078d9de8f1dd99e1bfb090f18491b186967883',
'com.doomonafireball.betterpickers:library:132ecd685c95a99e7377c4e27bfadbb2d7ed0bea995944060cd62d4369fdaf3d',
'com.madgag.spongycastle:prov:b8c3fec3a59aac1aa04ccf4dad7179351e54ef7672f53f508151b614c131398a',
'com.commonsware.cwac:camera:dcc93ddbb2f0393114fa1f31a13fe9e6edfcf5dbe96b22bc4b66c7b15e179054',
'org.whispersystems:jobmanager:506f679fc2fcf7bb6d10f00f41d6f6ea0abf75c70dc95b913398661ad538a181',
'org.whispersystems:libpastelog:550d33c565380d90f4c671e7b8ed5f3a6da55a9fda468373177106b2eb5220b2',
'com.amulyakhare:com.amulyakhare.textdrawable:54c92b5fba38cfd316a07e5a30528068f45ce8515a6890f1297df4c401af5dcb',

View File

@@ -50,9 +50,15 @@ CallAudioManager::~CallAudioManager() {
__android_log_print(ANDROID_LOG_WARN, TAG, "Shutting down...");
microphoneReader.stop();
__android_log_print(ANDROID_LOG_WARN, TAG, "Stopping audio player...");
audioPlayer.stop();
__android_log_print(ANDROID_LOG_WARN, TAG, "Stopping jitter buffer...");
webRtcJitterBuffer.stop();
__android_log_print(ANDROID_LOG_WARN, TAG, "Freeing resources...");
if (sockAddr != NULL) {
free(sockAddr);
}

View File

@@ -15,7 +15,7 @@ class CallAudioManager {
private:
volatile int running;
volatile int finished;
int finished;
SLObjectItf engineObject;
SLEngineItf engineEngine;
AudioCodec audioCodec;

View File

@@ -1,5 +1,5 @@
#include "WebRtcJitterBuffer.h"
#include <time.h>
//#include <time.h>
#define TAG "WebRtcJitterBuffer"
@@ -15,9 +15,7 @@ int WebRtcJitterBuffer::init() {
webrtc::NetEq::Config config;
config.sample_rate_hz = 8000;
pthread_mutex_lock(&lock);
neteq = webrtc::NetEq::Create(config);
pthread_mutex_unlock(&lock);
if (neteq == NULL) {
__android_log_print(ANDROID_LOG_WARN, TAG, "Failed to construct NetEq!");
@@ -29,7 +27,8 @@ int WebRtcJitterBuffer::init() {
return -1;
}
// pthread_create(&stats, NULL, &WebRtcJitterBuffer::collectStats, this);
// pthread_t thread;
// pthread_create(&thread, NULL, &WebRtcJitterBuffer::collectStats, this);
return 0;
}
@@ -67,21 +66,13 @@ int WebRtcJitterBuffer::getAudio(short *rawData, int maxRawData) {
}
void WebRtcJitterBuffer::stop() {
// pthread_mutex_lock(&lock);
running = 0;
// pthread_cond_signal(&condition);
// pthread_mutex_unlock(&lock);
// pthread_join(stats, NULL);
}
void WebRtcJitterBuffer::collectStats() {
while (running) {
webrtc::NetEqNetworkStatistics stats;
pthread_mutex_lock(&lock);
neteq->NetworkStatistics(&stats);
pthread_mutex_unlock(&lock);
__android_log_print(ANDROID_LOG_WARN, "WebRtcJitterBuffer",
"Jitter Stats:\n{\n" \
@@ -107,21 +98,22 @@ void WebRtcJitterBuffer::collectStats() {
stats.clockdrift_ppm,
stats.added_zero_samples);
struct timespec timeToWait;
struct timeval now;
gettimeofday(&now, NULL);
timeToWait.tv_sec = now.tv_sec;
timeToWait.tv_nsec = now.tv_usec * 1000;
timeToWait.tv_sec += 30;
pthread_mutex_lock(&lock);
if (running) {
pthread_cond_timedwait(&condition, &lock, &timeToWait);
}
pthread_mutex_unlock(&lock);
// struct timespec timeToWait;
// struct timeval now;
// gettimeofday(&now, NULL);
//
// timeToWait.tv_sec = now.tv_sec;
// timeToWait.tv_nsec = now.tv_usec * 1000;
// timeToWait.tv_sec += 30;
//
// pthread_mutex_lock(&lock);
//
// if (running) {
// pthread_cond_timedwait(&condition, &lock, &timeToWait);
// }
//
// pthread_mutex_unlock(&lock);
sleep(30);
}
}

View File

@@ -18,10 +18,6 @@ private:
webrtc::NetEq *neteq;
WebRtcCodec webRtcCodec;
pthread_t stats;
pthread_mutex_t lock;
pthread_cond_t condition;
public:
WebRtcJitterBuffer(AudioCodec &codec);
~WebRtcJitterBuffer();

Binary file not shown.

Binary file not shown.

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<org.thoughtcrime.securesms.components.camera.CameraView
<org.thoughtcrime.securesms.components.camera.QuickCamera
android:id="@+id/quick_camera"
android:layout_width="match_parent"
android:layout_height="match_parent"

View File

@@ -47,7 +47,7 @@ public class CallAudioManager {
if (Build.VERSION.SDK_INT >= 11) {
ServiceUtil.getAudioManager(context).setMode(AudioManager.MODE_IN_COMMUNICATION);
} else {
ServiceUtil.getAudioManager(context).setMode(AudioManager.MODE_IN_CALL);
// ServiceUtil.getAudioManager(context).setMode(AudioManager.MODE_IN_CALL);
}
try {

View File

@@ -54,6 +54,7 @@ import android.widget.TextView;
import android.widget.Toast;
import com.afollestad.materialdialogs.AlertDialogWrapper;
import com.commonsware.cwac.camera.CameraHost.FailureReason;
import com.google.protobuf.ByteString;
import org.thoughtcrime.redphone.RedPhone;
@@ -1340,7 +1341,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
@Override
public void onCameraFail() {
public void onCameraFail(FailureReason reason) {
Toast.makeText(this, R.string.ConversationActivity_quick_camera_unavailable, Toast.LENGTH_SHORT).show();
quickAttachmentDrawer.hide(false);
quickAttachmentToggle.disable();

View File

@@ -1,70 +0,0 @@
package org.thoughtcrime.securesms.components.camera;
import android.annotation.TargetApi;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.Size;
import android.os.Build.VERSION;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@SuppressWarnings("deprecation")
public class CameraUtils {
@TargetApi(11)
public static @Nullable Size getPreferredPreviewSize(int orientation, int width, int height, @NonNull Camera camera) {
final Parameters parameters = camera.getParameters();
final Size preferredSize = VERSION.SDK_INT > 11
? parameters.getPreferredPreviewSizeForVideo()
: null;
return preferredSize == null ? getBestAspectPreviewSize(orientation, width, height, parameters)
: preferredSize;
}
/*
* modified from: https://github.com/commonsguy/cwac-camera/blob/master/camera/src/com/commonsware/cwac/camera/CameraUtils.java
*/
public static @Nullable Size getBestAspectPreviewSize(int displayOrientation,
int width,
int height,
Parameters parameters) {
double targetRatio = (double)width / height;
Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
if (displayOrientation == 90 || displayOrientation == 270) {
targetRatio = (double)height / width;
}
List<Size> sizes = parameters.getSupportedPreviewSizes();
Collections.sort(sizes, Collections.reverseOrder(new SizeComparator()));
for (Size size : sizes) {
double ratio = (double)size.width / size.height;
if (Math.abs(ratio - targetRatio) < minDiff) {
optimalSize = size;
minDiff = Math.abs(ratio - targetRatio);
}
}
return optimalSize;
}
private static class SizeComparator implements Comparator<Size> {
@Override
public int compare(Size lhs, Size rhs) {
int left = lhs.width * lhs.height;
int right = rhs.width * rhs.height;
if (left < right) return -1;
if (left > right) return 1;
else return 0;
}
}
}

View File

@@ -20,47 +20,44 @@ import android.app.Activity;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.Color;
import android.graphics.Rect;
import android.hardware.Camera;
import android.hardware.Camera.CameraInfo;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.Size;
import android.os.AsyncTask;
import android.hardware.Camera.PreviewCallback;
import android.os.Build;
import android.os.Build.VERSION;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.OrientationEventListener;
import android.view.Surface;
import android.view.View;
import android.widget.FrameLayout;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import com.commonsware.cwac.camera.CameraHost;
import com.commonsware.cwac.camera.CameraHost.FailureReason;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.jobqueue.Job;
import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.libaxolotl.util.guava.Optional;
@SuppressWarnings("deprecation")
public class CameraView extends FrameLayout {
private static final String TAG = CameraView.class.getSimpleName();
private final CameraSurfaceView surface;
private final OnOrientationChange onOrientationChange;
private @NonNull volatile Optional<Camera> camera = Optional.absent();
private volatile int cameraId = CameraInfo.CAMERA_FACING_BACK;
private boolean started;
private @Nullable CameraViewListener listener;
private int displayOrientation = -1;
private int outputOrientation = -1;
private PreviewStrategy previewStrategy = null;
private Camera.Size previewSize = null;
private volatile Camera camera = null;
private boolean inPreview = false;
private boolean cameraReady = false;
private CameraHost host = null;
private OnOrientationChange onOrientationChange = null;
private int displayOrientation = -1;
private int outputOrientation = -1;
private int cameraId = -1;
private int lastPictureOrientation = -1;
public CameraView(Context context) {
this(context, null);
@@ -74,41 +71,51 @@ public class CameraView extends FrameLayout {
super(context, attrs, defStyle);
setBackgroundColor(Color.BLACK);
if (isMultiCamera()) cameraId = CameraInfo.CAMERA_FACING_FRONT;
surface = new CameraSurfaceView(getContext());
onOrientationChange = new OnOrientationChange(context.getApplicationContext());
addView(surface);
}
public CameraHost getHost() {
return host;
}
public void setHost(CameraHost host) {
this.host = host;
if (host.getDeviceProfile().useTextureView()) {
previewStrategy = new TexturePreviewStrategy(this);
} else {
previewStrategy = new SurfacePreviewStrategy(this);
}
addView(previewStrategy.getWidget());
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void onResume() {
if (started) return;
started = true;
Log.w(TAG, "onResume() queued");
enqueueTask(new SerialAsyncTask<Camera>() {
@Override
protected @Nullable Camera onRunBackground() {
final CameraHost host = getHost();
submitTask(new SerializedAsyncTask<FailureReason>() {
@Override protected FailureReason onRunBackground() {
try {
cameraId = host.getCameraId();
if (cameraId >= 0) {
return Camera.open(cameraId);
camera = Camera.open(cameraId);
} else {
return null;
return FailureReason.NO_CAMERAS_REPORTED;
}
} catch (Exception e) {
return null;
return FailureReason.UNKNOWN;
}
return null;
}
@Override
protected void onPostMain(@Nullable Camera camera) {
if (camera == null) {
if (listener != null) listener.onCameraFail();
@Override protected void onPostMain(FailureReason result) {
if (result != null) {
host.onCameraFail(result);
return;
}
CameraView.this.camera = Optional.of(camera);
try {
cameraReady = true;
if (getActivity().getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
onOrientationChange.enable();
}
@@ -116,182 +123,227 @@ public class CameraView extends FrameLayout {
synchronized (CameraView.this) {
CameraView.this.notifyAll();
}
onCameraReady();
previewCreated();
initPreview();
requestLayout();
invalidate();
Log.w(TAG, "onResume() completed");
} catch (RuntimeException e) {
Log.w(TAG, "exception when starting camera preview", e);
onPause();
}
}
});
}
public void onPause() {
if (!started) return;
started = false;
Log.w(TAG, "onPause() queued");
final Optional<Camera> cameraToDestroy = camera;
enqueueTask(new SerialAsyncTask<Void>() {
@Override protected void onPreMain() {
camera = Optional.absent();
}
@Override protected Void onRunBackground() {
if (cameraToDestroy.isPresent()) {
stopPreview();
cameraToDestroy.get().release();
}
return null;
}
@Override protected void onPostMain(Void avoid) {
onOrientationChange.disable();
displayOrientation = -1;
outputOrientation = -1;
Log.w(TAG, "onPause() completed");
}
});
}
public boolean isStarted() {
return started;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0 && camera.isPresent()) {
final Size preferredPreviewSize = CameraUtils.getPreferredPreviewSize(displayOrientation,
getMeasuredWidth(),
getMeasuredHeight(),
camera.get());
final Parameters parameters = camera.get().getParameters();
if (preferredPreviewSize != null && !parameters.getPreviewSize().equals(preferredPreviewSize)) {
stopPreview();
parameters.setPreviewSize(preferredPreviewSize.width, preferredPreviewSize.height);
camera.get().setParameters(parameters);
requestLayout();
startPreview();
}
}
}
@SuppressWarnings("SuspiciousNameCombination")
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int width = r - l;
final int height = b - t;
final int previewWidth;
final int previewHeight;
if (camera.isPresent()) {
final Size previewSize = camera.get().getParameters().getPreviewSize();
if (displayOrientation == 90 || displayOrientation == 270) {
previewWidth = previewSize.height;
previewHeight = previewSize.width;
} else {
previewWidth = previewSize.width;
previewHeight = previewSize.height;
}
} else {
previewWidth = width;
previewHeight = height;
}
if (previewHeight == 0 || previewWidth == 0) {
Log.w(TAG, "skipping layout due to zero-width/height preview size");
return;
}
Log.w(TAG, "layout " + width + "x" + height + ", target " + previewWidth + "x" + previewHeight);
if (width * previewHeight > height * previewWidth) {
final int scaledChildHeight = previewHeight * width / previewWidth;
surface.layout(0, (height - scaledChildHeight) / 2, width, (height + scaledChildHeight) / 2);
} else {
final int scaledChildWidth = previewWidth * height / previewHeight;
surface.layout((width - scaledChildWidth) / 2, 0, (width + scaledChildWidth) / 2, height);
}
}
public void setListener(@Nullable CameraViewListener listener) {
this.listener = listener;
}
public boolean isMultiCamera() {
return Camera.getNumberOfCameras() > 1;
}
public boolean isRearCamera() {
return cameraId == CameraInfo.CAMERA_FACING_BACK;
}
public void flipCamera() {
if (Camera.getNumberOfCameras() > 1) {
cameraId = cameraId == CameraInfo.CAMERA_FACING_BACK
? CameraInfo.CAMERA_FACING_FRONT
: CameraInfo.CAMERA_FACING_BACK;
onPause();
onResume();
}
}
@TargetApi(14)
private void onCameraReady() {
if (!camera.isPresent()) return;
final Parameters parameters = camera.get().getParameters();
final List<String> focusModes = parameters.getSupportedFocusModes();
if (VERSION.SDK_INT >= 14) parameters.setRecordingHint(true);
if (VERSION.SDK_INT >= 14 && focusModes.contains(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
} else if (focusModes.contains(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
}
camera.get().setParameters(parameters);
enqueueTask(new PostInitializationTask<Void>() {
@Override protected void onPostMain(Void avoid) {
if (camera.isPresent()) {
} catch (RuntimeException re) {
Log.w(TAG, "exception when starting camera preview", re);
try {
camera.get().setPreviewDisplay(surface.getHolder());
startPreview();
} catch (IOException e) {
Log.w(TAG, e);
previewDestroyed();
} catch (RuntimeException re2) {
Log.w(TAG, "also failed to release camera", re2);
}
}
}
});
}
private void startPreview() {
if (camera.isPresent()) {
camera.get().startPreview();
public void onPause() {
Log.w(TAG, "onPause() queued");
submitTask(new SerializedAsyncTask<Void>() {
@Override protected void onPreMain() {
cameraReady = false;
}
@Override protected Void onRunBackground() {
previewDestroyed();
return null;
}
@Override protected void onPostMain(Void avoid) {
onOrientationChange.disable();
previewSize = null;
displayOrientation = -1;
outputOrientation = -1;
cameraId = -1;
lastPictureOrientation = -1;
Log.w(TAG, "onPause() completed");
}
});
}
// based on CameraPreview.java from ApiDemos
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0 && camera != null && cameraReady) {
Camera.Size newSize = null;
try {
if (getHost().getRecordingHint() != CameraHost.RecordingHint.STILL_ONLY) {
newSize = getHost().getPreferredPreviewSizeForVideo(getDisplayOrientation(),
getMeasuredWidth(),
getMeasuredHeight(),
camera.getParameters(),
null);
}
if (newSize == null || newSize.width * newSize.height < 65536) {
newSize = getHost().getPreviewSize(getDisplayOrientation(),
getMeasuredWidth(),
getMeasuredHeight(),
camera.getParameters());
}
} catch (Exception e) {
Log.e(TAG, "Could not work with camera parameters?", e);
// TODO get this out to library clients
}
if (newSize != null) {
if (previewSize == null) {
previewSize = newSize;
synchronized (this) { notifyAll(); }
} else if (previewSize.width != newSize.width || previewSize.height != newSize.height) {
if (inPreview) {
stopPreview();
}
previewSize = newSize;
synchronized (this) { notifyAll(); }
initPreview();
}
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
// based on CameraPreview.java from ApiDemos
@SuppressWarnings("SuspiciousNameCombination") @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (getChildCount() > 0) {
final View child = getChildAt(0);
final int width = r - l;
final int height = b - t;
final int previewWidth;
final int previewHeight;
// handle orientation
if (previewSize != null && (getDisplayOrientation() == 90 || getDisplayOrientation() == 270)) {
previewWidth = previewSize.height;
previewHeight = previewSize.width;
} else if (previewSize != null) {
previewWidth = previewSize.width;
previewHeight = previewSize.height;
} else {
previewWidth = width;
previewHeight = height;
}
if (previewHeight == 0 || previewWidth == 0) {
Log.w(TAG, "skipping layout due to zero-width/height preview size");
return;
}
boolean useFirstStrategy = (width * previewHeight > height * previewWidth);
boolean useFullBleed = getHost().useFullBleedPreview();
if ((useFirstStrategy && !useFullBleed) || (!useFirstStrategy && useFullBleed)) {
final int scaledChildWidth = previewWidth * height / previewHeight;
child.layout((width - scaledChildWidth) / 2, 0, (width + scaledChildWidth) / 2, height);
} else {
final int scaledChildHeight = previewHeight * width / previewWidth;
child.layout(0, (height - scaledChildHeight) / 2, width, (height + scaledChildHeight) / 2);
}
}
}
private void stopPreview() {
if (camera.isPresent()) {
camera.get().stopPreview();
public int getDisplayOrientation() {
return displayOrientation;
}
public void setOneShotPreviewCallback(PreviewCallback callback) {
if (camera != null) camera.setOneShotPreviewCallback(callback);
}
public @Nullable Camera.Parameters getCameraParameters() {
return camera == null || !cameraReady ? null : camera.getParameters();
}
void previewCreated() {
Log.w(TAG, "previewCreated() queued");
final CameraHost host = getHost();
submitTask(new PostInitializationTask<Void>() {
@Override protected void onPostMain(Void avoid) {
try {
if (camera != null) {
previewStrategy.attach(camera);
}
} catch (IOException e) {
host.handleException(e);
}
Log.w(TAG, "previewCreated() completed");
}
});
}
void previewDestroyed() {
try {
if (camera != null) {
previewStopped();
camera.release();
}
} finally {
camera = null;
}
}
private void previewStopped() {
if (inPreview) {
stopPreview();
}
}
@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) {
Camera.Parameters parameters = camera.getParameters();
parameters.setPreviewSize(previewSize.width, previewSize.height);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
parameters.setRecordingHint(getHost().getRecordingHint() != CameraHost.RecordingHint.STILL_ONLY);
}
camera.setParameters(getHost().adjustPreviewParameters(parameters));
startPreview();
requestLayout();
invalidate();
Log.w(TAG, "initPreview() completed");
}
}
});
}
private void startPreview() {
camera.startPreview();
inPreview = true;
getHost().autoFocusAvailable();
}
private void stopPreview() {
camera.startPreview();
inPreview = false;
getHost().autoFocusUnavailable();
camera.stopPreview();
}
// based on
// http://developer.android.com/reference/android/hardware/Camera.html#setDisplayOrientation(int)
// and http://stackoverflow.com/a/10383164/115145
private void setCameraDisplayOrientation() {
Camera.CameraInfo info = getCameraInfo();
Camera.CameraInfo info = new Camera.CameraInfo();
int rotation = getActivity().getWindowManager().getDefaultDisplay().getRotation();
int degrees = 0;
DisplayMetrics dm = new DisplayMetrics();
Camera.getCameraInfo(cameraId, info);
getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm);
switch (rotation) {
@@ -309,51 +361,64 @@ public class CameraView extends FrameLayout {
displayOrientation = (info.orientation - degrees + 360) % 360;
}
stopPreview();
camera.get().setDisplayOrientation(displayOrientation);
startPreview();
boolean wasInPreview = inPreview;
if (inPreview) {
stopPreview();
}
camera.setDisplayOrientation(displayOrientation);
if (wasInPreview) {
startPreview();
}
}
public int getCameraPictureOrientation() {
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(cameraId, info);
if (getActivity().getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
outputOrientation = getCameraPictureRotation(getActivity().getWindowManager()
.getDefaultDisplay()
.getOrientation());
} else if (getCameraInfo().facing == CameraInfo.CAMERA_FACING_FRONT) {
} else if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
outputOrientation = (360 - displayOrientation) % 360;
} else {
outputOrientation = displayOrientation;
}
if (lastPictureOrientation != outputOrientation) {
lastPictureOrientation = outputOrientation;
}
return outputOrientation;
}
private @NonNull CameraInfo getCameraInfo() {
final CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(cameraId, info);
return info;
}
// XXX this sucks
private Activity getActivity() {
return (Activity)getContext();
}
// based on:
// http://developer.android.com/reference/android/hardware/Camera.Parameters.html#setRotation(int)
public int getCameraPictureRotation(int orientation) {
final CameraInfo info = getCameraInfo();
final int rotation;
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(cameraId, info);
int rotation;
orientation = (orientation + 45) / 90 * 90;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
rotation = (info.orientation - orientation + 360) % 360;
} else {
}
else { // back-facing camera
rotation = (info.orientation + orientation) % 360;
}
return rotation;
}
Activity getActivity() {
return (Activity)getContext();
}
private class OnOrientationChange extends OrientationEventListener {
public OnOrientationChange(Context context) {
super(context);
@@ -362,18 +427,19 @@ public class CameraView extends FrameLayout {
@Override
public void onOrientationChanged(int orientation) {
if (camera.isPresent() && orientation != ORIENTATION_UNKNOWN) {
if (camera != null && orientation != ORIENTATION_UNKNOWN) {
int newOutputOrientation = getCameraPictureRotation(orientation);
if (newOutputOrientation != outputOrientation) {
outputOrientation = newOutputOrientation;
Camera.Parameters params = camera.get().getParameters();
Camera.Parameters params = camera.getParameters();
params.setRotation(outputOrientation);
try {
camera.get().setParameters(params);
camera.setParameters(params);
lastPictureOrientation = outputOrientation;
}
catch (Exception e) {
Log.e(TAG, "Exception updating camera parameters in orientation change", e);
@@ -383,66 +449,13 @@ public class CameraView extends FrameLayout {
}
}
public void takePicture(final Rect previewRect) {
if (!camera.isPresent() || camera.get().getParameters() == null) {
Log.w(TAG, "camera not in capture-ready state");
return;
}
camera.get().setOneShotPreviewCallback(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, final Camera camera) {
final int rotation = getCameraPictureOrientation();
final Size previewSize = camera.getParameters().getPreviewSize();
final Rect croppingRect = getCroppedRect(previewSize, previewRect, rotation);
Log.w(TAG, "previewSize: " + previewSize.width + "x" + previewSize.height);
Log.w(TAG, "data bytes: " + data.length);
Log.w(TAG, "previewFormat: " + camera.getParameters().getPreviewFormat());
Log.w(TAG, "croppingRect: " + croppingRect.toString());
Log.w(TAG, "rotation: " + rotation);
new RotatePreviewAsyncTask(previewSize, rotation, croppingRect).execute(data);
}
});
}
private Rect getCroppedRect(Size cameraPreviewSize, Rect visibleRect, int rotation) {
final int previewWidth = cameraPreviewSize.width;
final int previewHeight = cameraPreviewSize.height;
if (rotation % 180 > 0) rotateRect(visibleRect);
float scale = (float) previewWidth / visibleRect.width();
if (visibleRect.height() * scale > previewHeight) {
scale = (float) previewHeight / visibleRect.height();
}
final float newWidth = visibleRect.width() * scale;
final float newHeight = visibleRect.height() * scale;
final float centerX = (VERSION.SDK_INT < 14) ? previewWidth - newWidth / 2 : previewWidth / 2;
final float centerY = previewHeight / 2;
visibleRect.set((int) (centerX - newWidth / 2),
(int) (centerY - newHeight / 2),
(int) (centerX + newWidth / 2),
(int) (centerY + newHeight / 2));
if (rotation % 180 > 0) rotateRect(visibleRect);
return visibleRect;
}
@SuppressWarnings("SuspiciousNameCombination")
private void rotateRect(Rect rect) {
rect.set(rect.top, rect.left, rect.bottom, rect.right);
}
private void enqueueTask(SerialAsyncTask job) {
private void submitTask(SerializedAsyncTask job) {
ApplicationContext.getInstance(getContext()).getJobManager().add(job);
}
private static abstract class SerialAsyncTask<Result> extends Job {
private static abstract class SerializedAsyncTask<Result> extends Job {
public SerialAsyncTask() {
public SerializedAsyncTask() {
super(JobParameters.newBuilder().withGroupId(CameraView.class.getSimpleName()).create());
}
@@ -451,7 +464,7 @@ public class CameraView extends FrameLayout {
@Override public final void onRun() {
try {
onWait();
Util.runOnMainSync(new Runnable() {
runOnMainSync(new Runnable() {
@Override public void run() {
onPreMain();
}
@@ -459,7 +472,7 @@ public class CameraView extends FrameLayout {
final Result result = onRunBackground();
Util.runOnMainSync(new Runnable() {
runOnMainSync(new Runnable() {
@Override public void run() {
onPostMain(result);
}
@@ -475,61 +488,44 @@ public class CameraView extends FrameLayout {
@Override public void onCanceled() { }
private void runOnMainSync(final Runnable runnable) {
final CountDownLatch sync = new CountDownLatch(1);
Util.runOnMain(new Runnable() {
@Override public void run() {
try {
runnable.run();
} finally {
sync.countDown();
}
}
});
try {
sync.await();
} catch (InterruptedException ie) {
throw new AssertionError(ie);
}
}
protected void onWait() throws PreconditionsNotMetException {}
protected void onPreMain() {}
protected Result onRunBackground() { return null; }
protected void onPostMain(Result result) {}
}
private abstract class PostInitializationTask<Result> extends SerialAsyncTask<Result> {
private abstract class PostInitializationTask<Result> extends SerializedAsyncTask<Result> {
@Override protected void onWait() throws PreconditionsNotMetException {
synchronized (CameraView.this) {
if (!camera.isPresent()) {
if (!cameraReady) {
throw new PreconditionsNotMetException();
}
while (getMeasuredHeight() <= 0 || getMeasuredWidth() <= 0 || !surface.isReady()) {
Log.w(TAG, String.format("waiting. surface ready? %s", surface.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);
}
}
}
}
private class RotatePreviewAsyncTask extends AsyncTask<byte[], Void, byte[]> {
private final Size previewSize;
private final int rotation;
private final Rect croppingRect;
public RotatePreviewAsyncTask(Size previewSize, int rotation, Rect croppingRect) {
this.previewSize = previewSize;
this.rotation = rotation;
this.croppingRect = croppingRect;
}
@Override
protected byte[] doInBackground(byte[]... params) {
final byte[] data = params[0];
try {
return BitmapUtil.createFromNV21(data,
previewSize.width,
previewSize.height,
rotation,
croppingRect);
} catch (IOException e) {
return null;
}
}
@Override
protected void onPostExecute(byte[] imageBytes) {
if (imageBytes != null && listener != null) listener.onImageCapture(imageBytes);
}
}
private static class PreconditionsNotMetException extends Exception {}
public interface CameraViewListener {
void onImageCapture(@NonNull final byte[] imageBytes);
void onCameraFail();
}
}

View File

@@ -0,0 +1,12 @@
package org.thoughtcrime.securesms.components.camera;
import android.hardware.Camera;
import android.media.MediaRecorder;
import android.view.View;
import java.io.IOException;
@SuppressWarnings("deprecation")
public interface PreviewStrategy extends com.commonsware.cwac.camera.PreviewStrategy {
boolean isReady();
}

View File

@@ -26,7 +26,7 @@ import com.nineoldandroids.animation.ObjectAnimator;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.InputAwareLayout.InputView;
import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout;
import org.thoughtcrime.securesms.components.camera.CameraView.CameraViewListener;
import org.thoughtcrime.securesms.components.camera.QuickCamera.QuickCameraListener;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
@@ -36,7 +36,7 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
private final ViewDragHelper dragHelper;
private CameraView cameraView;
private QuickCamera quickCamera;
private int coverViewPosition;
private KeyboardAwareLinearLayout container;
private View coverView;
@@ -74,12 +74,12 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
private void initializeView() {
inflate(getContext(), R.layout.quick_attachment_drawer, this);
cameraView = ViewUtil.findById(this, R.id.quick_camera);
quickCamera = (QuickCamera) findViewById(R.id.quick_camera);
updateControlsView();
coverViewPosition = getChildCount();
controls.setVisibility(GONE);
cameraView.setVisibility(GONE);
quickCamera.setVisibility(GONE);
}
public static boolean isDeviceSupported(Context context) {
@@ -108,7 +108,7 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
this.rotation = rotation;
if (rotationChanged) {
if (isShowing()) {
cameraView.onPause();
quickCamera.onPause();
}
updateControlsView();
setDrawerStateAndUpdate(drawerState, true);
@@ -123,13 +123,13 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
shutterButton = (ImageButton) controls.findViewById(R.id.shutter_button);
swapCameraButton = (ImageButton) controls.findViewById(R.id.swap_camera_button);
fullScreenButton = (ImageButton) controls.findViewById(R.id.fullscreen_button);
if (cameraView.isMultiCamera()) {
if (quickCamera.isMultipleCameras()) {
swapCameraButton.setVisibility(View.VISIBLE);
swapCameraButton.setOnClickListener(new CameraFlipClickListener());
}
shutterButton.setOnClickListener(new ShutterClickListener());
fullScreenButton.setOnClickListener(new FullscreenClickListener());
ViewUtil.swapChildInPlace(this, this.controls, controls, indexOfChild(cameraView) + 1);
ViewUtil.swapChildInPlace(this, this.controls, controls, indexOfChild(quickCamera) + 1);
this.controls = controls;
}
@@ -170,11 +170,11 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
int childLeft = paddingLeft;
int childBottom;
if (child == cameraView) {
if (child == quickCamera) {
childTop = computeCameraTopPosition(slideOffset);
childBottom = childTop + childHeight;
if (cameraView.getMeasuredWidth() < getMeasuredWidth())
childLeft = (getMeasuredWidth() - cameraView.getMeasuredWidth()) / 2 + paddingLeft;
if (quickCamera.getMeasuredWidth() < getMeasuredWidth())
childLeft = (getMeasuredWidth() - quickCamera.getMeasuredWidth()) / 2 + paddingLeft;
} else if (child == controls) {
childBottom = getMeasuredHeight();
} else {
@@ -271,14 +271,14 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
ViewCompat.postInvalidateOnAnimation(this);
}
if (slideOffset == 0 && cameraView.isStarted()) {
cameraView.onPause();
if (slideOffset == 0 && quickCamera.isStarted()) {
quickCamera.onPause();
controls.setVisibility(GONE);
cameraView.setVisibility(GONE);
} else if (slideOffset != 0 && !cameraView.isStarted() & !paused) {
quickCamera.setVisibility(GONE);
} else if (slideOffset != 0 && !quickCamera.isStarted() & !paused) {
controls.setVisibility(VISIBLE);
cameraView.setVisibility(VISIBLE);
cameraView.onResume();
quickCamera.setVisibility(VISIBLE);
quickCamera.onResume();
}
}
@@ -335,10 +335,10 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
public void setListener(AttachmentDrawerListener listener) {
this.listener = listener;
if (cameraView != null) cameraView.setListener(listener);
if (quickCamera != null) quickCamera.setQuickCameraListener(listener);
}
public interface AttachmentDrawerListener extends CameraViewListener {
public interface AttachmentDrawerListener extends QuickCameraListener {
void onAttachmentDrawerStateChanged(DrawerState drawerState);
}
@@ -391,8 +391,8 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
int slideOffset = getTargetSlideOffset();
dragHelper.captureChildView(coverView, 0);
dragHelper.settleCapturedViewAt(coverView.getLeft(), computeCoverTopPosition(slideOffset));
dragHelper.captureChildView(cameraView, 0);
dragHelper.settleCapturedViewAt(cameraView.getLeft(), computeCameraTopPosition(slideOffset));
dragHelper.captureChildView(quickCamera, 0);
dragHelper.settleCapturedViewAt(quickCamera.getLeft(), computeCameraTopPosition(slideOffset));
ViewCompat.postInvalidateOnAnimation(QuickAttachmentDrawer.this);
}
}
@@ -455,13 +455,13 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
@SuppressWarnings("ResourceType")
private boolean isDragViewUnder(int x, int y) {
int[] viewLocation = new int[2];
cameraView.getLocationOnScreen(viewLocation);
quickCamera.getLocationOnScreen(viewLocation);
int[] parentLocation = new int[2];
this.getLocationOnScreen(parentLocation);
int screenX = parentLocation[0] + x;
int screenY = parentLocation[1] + y;
return screenX >= viewLocation[0] && screenX < viewLocation[0] + cameraView.getWidth() &&
screenY >= viewLocation[1] && screenY < viewLocation[1] + cameraView.getHeight();
return screenX >= viewLocation[0] && screenX < viewLocation[0] + quickCamera.getWidth() &&
screenY >= viewLocation[1] && screenY < viewLocation[1] + quickCamera.getHeight();
}
private int computeCameraTopPosition(int slideOffset) {
@@ -469,7 +469,7 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
return getPaddingTop();
}
final int baseCameraTop = (cameraView.getMeasuredHeight() - halfExpandedHeight) / 2;
final int baseCameraTop = (quickCamera.getMeasuredHeight() - halfExpandedHeight) / 2;
final int baseOffset = getMeasuredHeight() - slideOffset - baseCameraTop;
final float slop = Util.clamp((float)(slideOffset - halfExpandedHeight) / (getMeasuredHeight() - halfExpandedHeight),
0f,
@@ -502,12 +502,12 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
public void onPause() {
paused = true;
cameraView.onPause();
quickCamera.onPause();
}
public void onResume() {
paused = false;
if (drawerState.isVisible()) cameraView.onResume();
if (drawerState.isVisible()) quickCamera.onResume();
}
public enum DrawerState {
@@ -522,18 +522,18 @@ public class QuickAttachmentDrawer extends ViewGroup implements InputView {
@Override
public void onClick(View v) {
boolean crop = drawerState != DrawerState.FULL_EXPANDED;
int imageHeight = crop ? getContainer().getKeyboardHeight() : cameraView.getMeasuredHeight();
Rect previewRect = new Rect(0, 0, cameraView.getMeasuredWidth(), imageHeight);
cameraView.takePicture(previewRect);
int imageHeight = crop ? getContainer().getKeyboardHeight() : quickCamera.getMeasuredHeight();
Rect previewRect = new Rect(0, 0, quickCamera.getMeasuredWidth(), imageHeight);
quickCamera.takePicture(previewRect);
}
}
private class CameraFlipClickListener implements OnClickListener {
@Override
public void onClick(View v) {
cameraView.flipCamera();
swapCameraButton.setImageResource(cameraView.isRearCamera() ? R.drawable.quick_camera_front
: R.drawable.quick_camera_rear);
quickCamera.swapCamera();
swapCameraButton.setImageResource(quickCamera.isRearCamera() ? R.drawable.quick_camera_front
: R.drawable.quick_camera_rear);
}
}

View File

@@ -0,0 +1,207 @@
package org.thoughtcrime.securesms.components.camera;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Rect;
import android.hardware.Camera;
import android.hardware.Camera.CameraInfo;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.Size;
import android.os.AsyncTask;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.util.Log;
import com.commonsware.cwac.camera.CameraHost.FailureReason;
import com.commonsware.cwac.camera.SimpleCameraHost;
import org.thoughtcrime.securesms.util.BitmapUtil;
import java.io.IOException;
import java.util.List;
@SuppressWarnings("deprecation") public class QuickCamera extends CameraView {
private static final String TAG = QuickCamera.class.getSimpleName();
private QuickCameraListener listener;
private boolean capturing;
private boolean started;
private QuickCameraHost cameraHost;
public QuickCamera(Context context) {
this(context, null);
}
public QuickCamera(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public QuickCamera(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
cameraHost = new QuickCameraHost(context);
setHost(cameraHost);
}
@Override
public void onResume() {
if (started) return;
super.onResume();
started = true;
}
@Override
public void onPause() {
if (!started) return;
super.onPause();
started = false;
}
public boolean isStarted() {
return started;
}
public void takePicture(final Rect previewRect) {
if (capturing) {
Log.w(TAG, "takePicture() called while previous capture pending.");
return;
}
final Parameters cameraParameters = getCameraParameters();
if (cameraParameters == null) {
Log.w(TAG, "camera not in capture-ready state");
return;
}
setOneShotPreviewCallback(new Camera.PreviewCallback() {
@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);
Log.w(TAG, "previewSize: " + previewSize.width + "x" + previewSize.height);
Log.w(TAG, "previewFormat: " + cameraParameters.getPreviewFormat());
Log.w(TAG, "croppingRect: " + croppingRect.toString());
Log.w(TAG, "rotation: " + rotation);
new AsyncTask<byte[], Void, byte[]>() {
@Override
protected byte[] doInBackground(byte[]... params) {
byte[] data = params[0];
try {
return BitmapUtil.createFromNV21(data,
previewSize.width,
previewSize.height,
rotation,
croppingRect);
} catch (IOException e) {
return null;
}
}
@Override
protected void onPostExecute(byte[] imageBytes) {
capturing = false;
if (imageBytes != null && listener != null) listener.onImageCapture(imageBytes);
}
}.execute(data);
}
});
}
private Rect getCroppedRect(Size cameraPreviewSize, Rect visibleRect, int rotation) {
final int previewWidth = cameraPreviewSize.width;
final int previewHeight = cameraPreviewSize.height;
if (rotation % 180 > 0) rotateRect(visibleRect);
float scale = (float) previewWidth / visibleRect.width();
if (visibleRect.height() * scale > previewHeight) {
scale = (float) previewHeight / visibleRect.height();
}
final float newWidth = visibleRect.width() * scale;
final float newHeight = visibleRect.height() * scale;
final float centerX;
final float centerY = previewHeight / 2;
if (VERSION.SDK_INT < VERSION_CODES.ICE_CREAM_SANDWICH) {
centerX = previewWidth - newWidth / 2;
} else {
centerX = previewWidth / 2;
}
visibleRect.set((int) (centerX - newWidth / 2),
(int) (centerY - newHeight / 2),
(int) (centerX + newWidth / 2),
(int) (centerY + newHeight / 2));
if (rotation % 180 > 0) rotateRect(visibleRect);
return visibleRect;
}
@SuppressWarnings("SuspiciousNameCombination")
private void rotateRect(Rect rect) {
rect.set(rect.top, rect.left, rect.bottom, rect.right);
}
public void setQuickCameraListener(QuickCameraListener listener) {
this.listener = listener;
}
public boolean isMultipleCameras() {
return Camera.getNumberOfCameras() > 1;
}
public boolean isRearCamera() {
return cameraHost.getCameraId() == Camera.CameraInfo.CAMERA_FACING_BACK;
}
public void swapCamera() {
cameraHost.swapCameraId();
onPause();
onResume();
}
public interface QuickCameraListener {
void onImageCapture(@NonNull final byte[] imageBytes);
void onCameraFail(FailureReason reason);
}
private class QuickCameraHost extends SimpleCameraHost {
int cameraId = CameraInfo.CAMERA_FACING_BACK;
public QuickCameraHost(Context context) {
super(context);
}
@TargetApi(VERSION_CODES.ICE_CREAM_SANDWICH) @Override
public Parameters adjustPreviewParameters(Parameters parameters) {
List<String> focusModes = parameters.getSupportedFocusModes();
if (focusModes.contains(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
} else if (focusModes.contains(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
parameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
}
return parameters;
}
@Override
public int getCameraId() {
return cameraId;
}
public void swapCameraId() {
if (isMultipleCameras()) {
if (cameraId == CameraInfo.CAMERA_FACING_BACK) cameraId = CameraInfo.CAMERA_FACING_FRONT;
else cameraId = CameraInfo.CAMERA_FACING_BACK;
}
}
@Override
public void onCameraFail(FailureReason reason) {
super.onCameraFail(reason);
if (listener != null) listener.onCameraFail(reason);
}
}
}

View File

@@ -0,0 +1,82 @@
/***
Copyright (c) 2013 CommonsWare, LLC
Licensed under the Apache License, Version 2.0 (the "License"); you may
not use this file except in compliance with the License. You may obtain
a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package org.thoughtcrime.securesms.components.camera;
import android.hardware.Camera;
import android.media.MediaRecorder;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import java.io.IOException;
class SurfacePreviewStrategy implements PreviewStrategy,
SurfaceHolder.Callback {
private final static String TAG = SurfacePreviewStrategy.class.getSimpleName();
private final CameraView cameraView;
private SurfaceView preview=null;
private SurfaceHolder previewHolder=null;
private boolean ready = false;
@SuppressWarnings("deprecation")
SurfacePreviewStrategy(CameraView cameraView) {
this.cameraView=cameraView;
preview=new SurfaceView(cameraView.getContext());
previewHolder=preview.getHolder();
previewHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
previewHolder.addCallback(this);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.w(TAG, "surfaceCreated()");
ready = true;
synchronized (cameraView) { cameraView.notifyAll(); }
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {
Log.w(TAG, "surfaceChanged()");
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.w(TAG, "surfaceDestroyed()");
cameraView.onPause();
}
@Override
public void attach(Camera camera) throws IOException {
Log.w(TAG, "attach(Camera)");
camera.setPreviewDisplay(previewHolder);
}
@Override
public void attach(MediaRecorder recorder) {
recorder.setPreviewDisplay(previewHolder.getSurface());
}
@Override
public View getWidget() {
return(preview);
}
@Override
public boolean isReady() {
return ready;
}
}

View File

@@ -0,0 +1,93 @@
package org.thoughtcrime.securesms.components.camera;
/***
Copyright (c) 2013 CommonsWare, LLC
Licensed under the Apache License, Version 2.0 (the "License"); you may
not use this file except in compliance with the License. You may obtain
a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import android.annotation.TargetApi;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.media.MediaRecorder;
import android.os.Build;
import android.util.Log;
import android.view.TextureView;
import android.view.View;
import java.io.IOException;
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
class TexturePreviewStrategy implements PreviewStrategy,
TextureView.SurfaceTextureListener {
private final static String TAG = TexturePreviewStrategy.class.getSimpleName();
private final CameraView cameraView;
private TextureView widget=null;
private SurfaceTexture surface=null;
TexturePreviewStrategy(CameraView cameraView) {
this.cameraView=cameraView;
widget=new TextureView(cameraView.getContext());
widget.setSurfaceTextureListener(this);
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface,
int width, int height) {
Log.w(TAG, "onSurfaceTextureAvailable()");
this.surface=surface;
synchronized (cameraView) { cameraView.notifyAll(); }
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface,
int width, int height) {
Log.w(TAG, "onSurfaceTextureChanged()");
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
Log.w(TAG, "onSurfaceTextureDestroyed()");
cameraView.onPause();
return(true);
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
// no-op
}
@Override
public void attach(Camera camera) throws IOException {
camera.setPreviewTexture(surface);
}
@Override
public void attach(MediaRecorder recorder) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
// no-op
}
else {
throw new IllegalStateException(
"Cannot use TextureView with MediaRecorder");
}
}
@Override
public boolean isReady() {
return widget.isAvailable();
}
@Override
public View getWidget() {
return(widget);
}
}

View File

@@ -23,13 +23,11 @@ import android.content.OperationApplicationException;
import android.database.Cursor;
import android.database.CursorWrapper;
import android.database.MatrixCursor;
import android.database.MergeCursor;
import android.net.Uri;
import android.os.Build;
import android.os.RemoteException;
import android.provider.BaseColumns;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.RawContacts;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@@ -38,10 +36,6 @@ import android.util.Log;
import android.util.Pair;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.TextSecureDirectory;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.util.DirectoryHelper;
import org.thoughtcrime.securesms.util.DirectoryHelper.UserCapabilities.Capability;
import org.whispersystems.libaxolotl.util.guava.Optional;
import org.whispersystems.textsecure.api.util.InvalidNumberException;
import org.whispersystems.textsecure.api.util.PhoneNumberFormatter;
@@ -119,9 +113,10 @@ public class ContactsDatabase {
for (String number : e164numbers) {
if (!currentContacts.containsKey(number)) {
Optional<SystemContactInfo> systemContactInfo = getSystemContactInfo(number);
Optional<SystemContactInfo> systemContactInfo = getSystemContactInfo(number, localNumber);
if (systemContactInfo.isPresent()) {
Log.w(TAG, "Adding number: " + number);
addedNumbers.add(number);
addTextSecureRawContact(operations, account, systemContactInfo.get().number, systemContactInfo.get().id);
}
@@ -280,7 +275,9 @@ public class ContactsDatabase {
return newNumberCursor;
}
private Optional<SystemContactInfo> getSystemContactInfo(String e164number) {
private Optional<SystemContactInfo> getSystemContactInfo(@NonNull String e164number,
@NonNull String localNumber)
{
Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(e164number));
String[] projection = {ContactsContract.PhoneLookup.NUMBER,
ContactsContract.PhoneLookup._ID,
@@ -291,17 +288,26 @@ public class ContactsDatabase {
try {
numberCursor = context.getContentResolver().query(uri, projection, null, null, null);
if (numberCursor != null && numberCursor.moveToNext()) {
idCursor = context.getContentResolver().query(RawContacts.CONTENT_URI,
new String[] {RawContacts._ID},
RawContacts.CONTACT_ID + " = ? ",
new String[] {String.valueOf(numberCursor.getLong(1))},
null);
while (numberCursor != null && numberCursor.moveToNext()) {
try {
String systemNumber = numberCursor.getString(0);
String canonicalizedSystemNumber = PhoneNumberFormatter.formatNumber(systemNumber, localNumber);
if (idCursor != null && idCursor.moveToNext()) {
return Optional.of(new SystemContactInfo(numberCursor.getString(2),
numberCursor.getString(0),
idCursor.getLong(0)));
if (canonicalizedSystemNumber.equals(e164number)) {
idCursor = context.getContentResolver().query(RawContacts.CONTENT_URI,
new String[] {RawContacts._ID},
RawContacts.CONTACT_ID + " = ? ",
new String[] {String.valueOf(numberCursor.getLong(1))},
null);
if (idCursor != null && idCursor.moveToNext()) {
return Optional.of(new SystemContactInfo(numberCursor.getString(2),
numberCursor.getString(0),
idCursor.getLong(0)));
}
}
} catch (InvalidNumberException e) {
Log.w(TAG, e);
}
}
} finally {

View File

@@ -25,6 +25,8 @@ public class ContactsSyncAdapter extends AbstractThreadedSyncAdapter {
public void onPerformSync(Account account, Bundle extras, String authority,
ContentProviderClient provider, SyncResult syncResult)
{
Log.w(TAG, "onPerformSync(" + authority +")");
try {
DirectoryHelper.refreshDirectory(getContext(), KeyCachingService.getMasterSecret(getContext()));
} catch (IOException e) {

View File

@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import android.os.PowerManager;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.SecurityEvent;
import org.thoughtcrime.securesms.service.KeyCachingService;
@@ -26,6 +27,7 @@ public class DirectoryRefreshJob extends ContextJob {
@Override
public void onRun() throws IOException {
Log.w("DirectoryRefreshJob", "DirectoryRefreshJob.onRun()");
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Directory Refresh");