diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 7566503dda..6dfcbb52d5 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -131,12 +131,6 @@
-
-
-
-
-
-
-
diff --git a/build.gradle b/build.gradle
index deb4bc1193..bd43b598f6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -50,6 +50,7 @@ dependencies {
compile 'com.android.support:appcompat-v7:22.2.1'
compile 'com.android.support:recyclerview-v7:22.2.1'
compile 'com.android.support:design:22.2.1'
+ compile 'com.android.support:cardview-v7:22.2.1'
compile 'com.melnykov:floatingactionbutton:1.3.0'
compile 'com.google.zxing:android-integration:3.1.0'
compile ('com.android.support:support-v4-preferencefragment:1.0.0@aar'){
@@ -71,6 +72,7 @@ dependencies {
compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
compile 'org.whispersystems:textsecure-android:1.8.3'
compile 'com.h6ah4i.android.compat:mulsellistprefcompat:1.0.0'
+ compile 'com.google.zxing:core:3.2.1'
testCompile 'junit:junit:4.12'
testCompile 'org.assertj:assertj-core:1.7.1'
@@ -109,6 +111,7 @@ dependencyVerification {
'com.android.support:appcompat-v7:4b5ccba8c4557ef04f99aa0a80f8aa7d50f05f926a709010a54afd5c878d3618',
'com.android.support:recyclerview-v7:b0f530a5b14334d56ce0de85527ffe93ac419bc928e2884287ce1dddfedfb505',
'com.android.support:design:58be3ca6a73789615f7ece0937d2f683b98b594bb90aa10565fa760fb10b07ee',
+ 'com.android.support:cardview-v7:2c2354761a4e20ba451ae903ab808f15c9acc8343b1e74001869c2d0a672c1fc',
'com.melnykov:floatingactionbutton:15d58d4fac0f7a288d0e5301bbaf501a146f5b3f5921277811bf99bd3b397263',
'com.google.zxing:android-integration:89e56aadf1164bd71e57949163c53abf90af368b51669c0d4a47a163335f95c4',
'com.android.support:support-v4-preferencefragment:5470f5872514a6226fa1fc6f4e000991f38805691c534cf0bd2778911fc773ad',
@@ -121,6 +124,7 @@ dependencyVerification {
'com.amulyakhare:com.amulyakhare.textdrawable:54c92b5fba38cfd316a07e5a30528068f45ce8515a6890f1297df4c401af5dcb',
'org.whispersystems:textsecure-android:a08cdd73aaaca6d3e868a93522c02d6a159551735f7048b1f3a53582e10c8ec2',
'com.h6ah4i.android.compat:mulsellistprefcompat:47167c5cb796de1a854788e9ff318358e36c8fb88123baaa6e38fb78511dfabe',
+ 'com.google.zxing:core:b4d82452e7a6bf6ec2698904b332431717ed8f9a850224f295aec89de80f2259',
'com.android.support:support-annotations:104f353b53d5dd8d64b2f77eece4b37f6b961de9732eb6b706395e91033ec70a',
'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a',
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
diff --git a/res/drawable-hdpi/ic_add_white_original_24dp.png b/res/drawable-hdpi/ic_add_white_original_24dp.png
new file mode 100644
index 0000000000..481643ecd5
Binary files /dev/null and b/res/drawable-hdpi/ic_add_white_original_24dp.png differ
diff --git a/res/drawable-hdpi/ic_devices_white.png b/res/drawable-hdpi/ic_devices_white.png
new file mode 100644
index 0000000000..38d1c0c046
Binary files /dev/null and b/res/drawable-hdpi/ic_devices_white.png differ
diff --git a/res/drawable-hdpi/ic_laptop_black_32dp.png b/res/drawable-hdpi/ic_laptop_black_32dp.png
new file mode 100644
index 0000000000..c87e01d547
Binary files /dev/null and b/res/drawable-hdpi/ic_laptop_black_32dp.png differ
diff --git a/res/drawable-hdpi/ic_laptop_light_32dp.png b/res/drawable-hdpi/ic_laptop_light_32dp.png
new file mode 100644
index 0000000000..0f8d1680fb
Binary files /dev/null and b/res/drawable-hdpi/ic_laptop_light_32dp.png differ
diff --git a/res/drawable-mdpi/ic_add_white_original_24dp.png b/res/drawable-mdpi/ic_add_white_original_24dp.png
new file mode 100644
index 0000000000..977dd3427a
Binary files /dev/null and b/res/drawable-mdpi/ic_add_white_original_24dp.png differ
diff --git a/res/drawable-mdpi/ic_devices_white.png b/res/drawable-mdpi/ic_devices_white.png
new file mode 100644
index 0000000000..32fad78388
Binary files /dev/null and b/res/drawable-mdpi/ic_devices_white.png differ
diff --git a/res/drawable-mdpi/ic_laptop_black_32dp.png b/res/drawable-mdpi/ic_laptop_black_32dp.png
new file mode 100644
index 0000000000..795f9991d3
Binary files /dev/null and b/res/drawable-mdpi/ic_laptop_black_32dp.png differ
diff --git a/res/drawable-mdpi/ic_laptop_light_32dp.png b/res/drawable-mdpi/ic_laptop_light_32dp.png
new file mode 100644
index 0000000000..58d056ffd6
Binary files /dev/null and b/res/drawable-mdpi/ic_laptop_light_32dp.png differ
diff --git a/res/drawable-xhdpi/ic_add_white_original_24dp.png b/res/drawable-xhdpi/ic_add_white_original_24dp.png
new file mode 100644
index 0000000000..67042105d2
Binary files /dev/null and b/res/drawable-xhdpi/ic_add_white_original_24dp.png differ
diff --git a/res/drawable-xhdpi/ic_devices_white.png b/res/drawable-xhdpi/ic_devices_white.png
new file mode 100644
index 0000000000..b77159e119
Binary files /dev/null and b/res/drawable-xhdpi/ic_devices_white.png differ
diff --git a/res/drawable-xhdpi/ic_laptop_black_32dp.png b/res/drawable-xhdpi/ic_laptop_black_32dp.png
new file mode 100644
index 0000000000..c1228164b2
Binary files /dev/null and b/res/drawable-xhdpi/ic_laptop_black_32dp.png differ
diff --git a/res/drawable-xhdpi/ic_laptop_light_32dp.png b/res/drawable-xhdpi/ic_laptop_light_32dp.png
new file mode 100644
index 0000000000..34e1a1efb4
Binary files /dev/null and b/res/drawable-xhdpi/ic_laptop_light_32dp.png differ
diff --git a/res/drawable-xxhdpi/ic_add_white_original_24dp.png b/res/drawable-xxhdpi/ic_add_white_original_24dp.png
new file mode 100644
index 0000000000..72cedcad4f
Binary files /dev/null and b/res/drawable-xxhdpi/ic_add_white_original_24dp.png differ
diff --git a/res/drawable-xxhdpi/ic_devices_white.png b/res/drawable-xxhdpi/ic_devices_white.png
new file mode 100644
index 0000000000..e2ebaaccef
Binary files /dev/null and b/res/drawable-xxhdpi/ic_devices_white.png differ
diff --git a/res/drawable-xxhdpi/ic_laptop_black_32dp.png b/res/drawable-xxhdpi/ic_laptop_black_32dp.png
new file mode 100644
index 0000000000..c86e7c4ada
Binary files /dev/null and b/res/drawable-xxhdpi/ic_laptop_black_32dp.png differ
diff --git a/res/drawable-xxhdpi/ic_laptop_light_32dp.png b/res/drawable-xxhdpi/ic_laptop_light_32dp.png
new file mode 100644
index 0000000000..f13d25e424
Binary files /dev/null and b/res/drawable-xxhdpi/ic_laptop_light_32dp.png differ
diff --git a/res/drawable-xxxhdpi/ic_add_white_original_24dp.png b/res/drawable-xxxhdpi/ic_add_white_original_24dp.png
new file mode 100644
index 0000000000..2bef059583
Binary files /dev/null and b/res/drawable-xxxhdpi/ic_add_white_original_24dp.png differ
diff --git a/res/drawable-xxxhdpi/ic_devices_white.png b/res/drawable-xxxhdpi/ic_devices_white.png
new file mode 100644
index 0000000000..e3f2216f4d
Binary files /dev/null and b/res/drawable-xxxhdpi/ic_devices_white.png differ
diff --git a/res/layout/device_add_fragment.xml b/res/layout/device_add_fragment.xml
new file mode 100644
index 0000000000..019e32cc2f
--- /dev/null
+++ b/res/layout/device_add_fragment.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/device_link_fragment.xml b/res/layout/device_link_fragment.xml
new file mode 100644
index 0000000000..f67863d6fe
--- /dev/null
+++ b/res/layout/device_link_fragment.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/device_list_fragment.xml b/res/layout/device_list_fragment.xml
index 4eb43f68e7..7b73e96b13 100644
--- a/res/layout/device_list_fragment.xml
+++ b/res/layout/device_list_fragment.xml
@@ -2,9 +2,9 @@
+ xmlns:tools="http://schemas.android.com/tools"
+ xmlns:fab="http://schemas.android.com/apk/res-auto"
+ android:orientation="vertical">
+ android:text="@string/device_list_fragment__no_devices_linked"
+ android:paddingLeft="16dip"
+ android:paddingRight="16dip"
+ android:layout_weight="1"
+ tools:visibility="visible"/>
+ android:drawSelectorOnTop="false"
+ android:paddingLeft="16dip"
+ android:paddingRight="16dip"
+ tools:visibility="gone"/>
+
\ No newline at end of file
diff --git a/res/transition/fragment_shared.xml b/res/transition/fragment_shared.xml
new file mode 100644
index 0000000000..42095a7762
--- /dev/null
+++ b/res/transition/fragment_shared.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 68453ca171..433040b358 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -147,4 +147,13 @@
+
+
+
+
+
+
+
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index e845da73ef..9890a17ed8 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -345,12 +345,12 @@
Link this device?
It will be able to
- - Read all your messages
- \n- Send messages in your name
+ • Read all your messages
+ \n• Send messages in your name
Linking device
Linking new device...
- Device linked!
+ Device approved!
No device found.
Network error.
Invalid QR code.
@@ -1128,6 +1128,9 @@
Transport icon
+ Scan the QR code displayed on the device to link
+ Link device
+ Link new device
diff --git a/res/values/themes.xml b/res/values/themes.xml
index 3d65e4a0d6..fe39dfaa7d 100644
--- a/res/values/themes.xml
+++ b/res/values/themes.xml
@@ -188,7 +188,7 @@
- @drawable/ic_app_protection_black
- @drawable/ic_brightness_6_black
- @drawable/ic_forum_black_32dp
- - @drawable/ic_devices_black_48dp
+ - @drawable/ic_laptop_black_32dp
- @drawable/ic_advanced_black
- @style/BetterPickersDialogFragment.Light
@@ -301,7 +301,7 @@
- @drawable/ic_app_protection_gray
- @drawable/ic_brightness_6_gray
- @drawable/ic_forum_grey_32dp
- - @drawable/ic_devices_grey600_48dp
+ - @drawable/ic_laptop_light_32dp
- @drawable/ic_advanced_gray
- @style/BetterPickersDialogFragment
diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml
index 459324a05b..1fff7058fc 100644
--- a/res/xml/preferences.xml
+++ b/res/xml/preferences.xml
@@ -21,11 +21,9 @@
android:title="@string/preferences__chats"
android:icon="?pref_ic_chats"/>
-
-
-
-
-
+
= Build.VERSION_CODES.LOLLIPOP) {
+ deviceAddFragment.setSharedElementReturnTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(R.transition.fragment_shared));
+ deviceAddFragment.setExitTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(android.R.transition.fade));
+
+ deviceLinkFragment.setSharedElementEnterTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(R.transition.fragment_shared));
+ deviceLinkFragment.setEnterTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(android.R.transition.fade));
+
+ getSupportFragmentManager().beginTransaction()
+ .addToBackStack(null)
+ .addSharedElement(deviceAddFragment.getDevicesImage(), "devices")
+ .replace(android.R.id.content, deviceLinkFragment)
+ .commit();
+
+ } else {
+ getSupportFragmentManager().beginTransaction()
+ .setCustomAnimations(R.anim.slide_from_bottom, R.anim.slide_to_bottom,
+ R.anim.slide_from_bottom, R.anim.slide_to_bottom)
+ .replace(android.R.id.content, deviceLinkFragment)
+ .addToBackStack(null)
+ .commit();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onLink(final Uri uri) {
+ new ProgressDialogAsyncTask(this,
+ R.string.DeviceProvisioningActivity_content_progress_title,
+ R.string.DeviceProvisioningActivity_content_progress_content)
+ {
+ private static final int SUCCESS = 0;
+ private static final int NO_DEVICE = 1;
+ private static final int NETWORK_ERROR = 2;
+ private static final int KEY_ERROR = 3;
+ private static final int LIMIT_EXCEEDED = 4;
+
+ @Override
+ protected Integer doInBackground(Void... params) {
+ try {
+ Context context = DeviceActivity.this;
+ TextSecureAccountManager accountManager = TextSecureCommunicationFactory.createManager(context);
+ String verificationCode = accountManager.getNewDeviceVerificationCode();
+ String ephemeralId = uri.getQueryParameter("uuid");
+ String publicKeyEncoded = uri.getQueryParameter("pub_key");
+ ECPublicKey publicKey = Curve.decodePoint(Base64.decode(publicKeyEncoded), 0);
+ IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context);
+
+ accountManager.addDevice(ephemeralId, publicKey, identityKeyPair, verificationCode);
+ TextSecurePreferences.setMultiDevice(context, true);
+ return SUCCESS;
+ } catch (NotFoundException e) {
+ Log.w(TAG, e);
+ return NO_DEVICE;
+ } catch (DeviceLimitExceededException e) {
+ Log.w(TAG, e);
+ return LIMIT_EXCEEDED;
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ return NETWORK_ERROR;
+ } catch (InvalidKeyException e) {
+ Log.w(TAG, e);
+ return KEY_ERROR;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Integer result) {
+ super.onPostExecute(result);
+
+ Context context = DeviceActivity.this;
+
+ switch (result) {
+ case SUCCESS:
+ Toast.makeText(context, R.string.DeviceProvisioningActivity_content_progress_success, Toast.LENGTH_SHORT).show();
+ finish();
+ return;
+ case NO_DEVICE:
+ Toast.makeText(context, R.string.DeviceProvisioningActivity_content_progress_no_device, Toast.LENGTH_LONG).show();
+ break;
+ case NETWORK_ERROR:
+ Toast.makeText(context, R.string.DeviceProvisioningActivity_content_progress_network_error, Toast.LENGTH_LONG).show();
+ break;
+ case KEY_ERROR:
+ Toast.makeText(context, R.string.DeviceProvisioningActivity_content_progress_key_error, Toast.LENGTH_LONG).show();
+ break;
+ case LIMIT_EXCEEDED:
+ Toast.makeText(context, R.string.DeviceProvisioningActivity_sorry_you_have_too_many_devices_linked_already, Toast.LENGTH_LONG).show();
+ break;
+ }
+
+ getSupportFragmentManager().popBackStackImmediate();
+ }
+ }.execute();
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/DeviceAddFragment.java b/src/org/thoughtcrime/securesms/DeviceAddFragment.java
new file mode 100644
index 0000000000..a4de3916b2
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/DeviceAddFragment.java
@@ -0,0 +1,220 @@
+package org.thoughtcrime.securesms;
+
+import android.animation.Animator;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewAnimationUtils;
+import android.view.ViewGroup;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import com.google.zxing.BinaryBitmap;
+import com.google.zxing.ChecksumException;
+import com.google.zxing.FormatException;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.PlanarYUVLuminanceSource;
+import com.google.zxing.Result;
+import com.google.zxing.common.HybridBinarizer;
+import com.google.zxing.qrcode.QRCodeReader;
+
+import org.thoughtcrime.securesms.components.camera.CameraView;
+import org.thoughtcrime.securesms.components.camera.CameraView.PreviewCallback;
+import org.thoughtcrime.securesms.components.camera.CameraView.PreviewFrame;
+import org.thoughtcrime.securesms.util.Util;
+import org.thoughtcrime.securesms.util.ViewUtil;
+
+public class DeviceAddFragment extends Fragment implements PreviewCallback {
+
+ private static final String TAG = DeviceAddFragment.class.getSimpleName();
+
+ private final QRCodeReader reader = new QRCodeReader();
+
+ private ViewGroup container;
+ private LinearLayout overlay;
+ private ImageView devicesImage;
+ private CameraView scannerView;
+ private PreviewFrame previewFrame;
+ private ScanningThread scanningThread;
+ private ScanListener scanListener;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
+ this.container = ViewUtil.inflate(inflater, viewGroup, R.layout.device_add_fragment);
+ this.overlay = ViewUtil.findById(this.container, R.id.overlay);
+ this.scannerView = ViewUtil.findById(this.container, R.id.scanner);
+ this.devicesImage = ViewUtil.findById(this.container, R.id.devices);
+ this.scannerView.onResume();
+ this.scannerView.setPreviewCallback(this);
+
+ if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ this.overlay.setOrientation(LinearLayout.HORIZONTAL);
+ } else {
+ this.overlay.setOrientation(LinearLayout.VERTICAL);
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ this.container.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom)
+ {
+ v.removeOnLayoutChangeListener(this);
+
+ Animator reveal = ViewAnimationUtils.createCircularReveal(v, right, bottom, 0, (int) Math.hypot(right, bottom));
+ reveal.setInterpolator(new DecelerateInterpolator(2f));
+ reveal.setDuration(800);
+ reveal.start();
+ }
+ });
+ }
+
+ return this.container;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ this.scannerView.onResume();
+ this.scannerView.setPreviewCallback(this);
+ this.previewFrame = null;
+ this.scanningThread = new ScanningThread();
+ this.scanningThread.start();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ this.scannerView.onPause();
+ this.scanningThread.stopScanning();
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfiguration) {
+ super.onConfigurationChanged(newConfiguration);
+
+ this.scannerView.onPause();
+
+ if (newConfiguration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ overlay.setOrientation(LinearLayout.HORIZONTAL);
+ } else {
+ overlay.setOrientation(LinearLayout.VERTICAL);
+ }
+
+ this.scannerView.onResume();
+ this.scannerView.setPreviewCallback(this);
+ }
+
+ @Override
+ public void onPreviewFrame(@NonNull PreviewFrame previewFrame) {
+ Context context = getActivity();
+
+ try {
+ if (context != null) {
+ synchronized (this) {
+ this.previewFrame = previewFrame;
+ this.notify();
+ }
+ }
+ } catch (RuntimeException e) {
+ Log.w(TAG, e);
+ }
+ }
+
+ public ImageView getDevicesImage() {
+ return devicesImage;
+ }
+
+ public void setScanListener(ScanListener scanListener) {
+ this.scanListener = scanListener;
+ }
+
+ private class ScanningThread extends Thread {
+
+ private boolean scanning = true;
+
+ @Override
+ public void run() {
+ while (true) {
+ PreviewFrame ourFrame;
+
+ synchronized (DeviceAddFragment.this) {
+ while (scanning && previewFrame == null) {
+ Util.wait(DeviceAddFragment.this, 0);
+ }
+
+ if (!scanning) return;
+ else ourFrame = previewFrame;
+
+ previewFrame = null;
+ }
+
+ String url = getUrl(ourFrame.getData(), ourFrame.getWidth(), ourFrame.getHeight(), ourFrame.getOrientation());
+
+ if (url != null && scanListener != null) {
+ Uri uri = Uri.parse(url);
+ scanListener.onUrlFound(uri);
+ return;
+ }
+ }
+ }
+
+ public void stopScanning() {
+ synchronized (DeviceAddFragment.this) {
+ scanning = false;
+ DeviceAddFragment.this.notify();
+ }
+ }
+
+ private @Nullable String getUrl(byte[] data, int width, int height, int orientation) {
+ try {
+ if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+ byte[] rotatedData = new byte[data.length];
+
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ rotatedData[x * height + height - y - 1] = data[x + y * width];
+ }
+ }
+
+ int tmp = width;
+ width = height;
+ height = tmp;
+ data = rotatedData;
+ }
+
+ PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(data, width, height,
+ 0, 0, width, height,
+ false);
+
+ BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
+
+ Result result = reader.decode(bitmap);
+
+ if (result != null) return result.getText();
+
+ } catch (NullPointerException | ChecksumException | FormatException e) {
+ Log.w(TAG, e);
+ } catch (NotFoundException e) {
+ // Thanks ZXing...
+ }
+
+ return null;
+ }
+ }
+
+ public interface ScanListener {
+ public void onUrlFound(Uri uri);
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/DeviceLinkFragment.java b/src/org/thoughtcrime/securesms/DeviceLinkFragment.java
new file mode 100644
index 0000000000..21487e2138
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/DeviceLinkFragment.java
@@ -0,0 +1,56 @@
+package org.thoughtcrime.securesms;
+
+import android.content.res.Configuration;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+public class DeviceLinkFragment extends Fragment implements View.OnClickListener {
+
+ private LinearLayout container;
+ private LinkClickedListener linkClickedListener;
+ private Uri uri;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
+ this.container = (LinearLayout) inflater.inflate(R.layout.device_link_fragment, container, false);
+ this.container.findViewById(R.id.link_device).setOnClickListener(this);
+
+ if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ container.setOrientation(LinearLayout.HORIZONTAL);
+ } else {
+ container.setOrientation(LinearLayout.VERTICAL);
+ }
+
+ return this.container;
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfiguration) {
+ if (newConfiguration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ container.setOrientation(LinearLayout.HORIZONTAL);
+ } else {
+ container.setOrientation(LinearLayout.VERTICAL);
+ }
+ }
+
+ public void setLinkClickedListener(Uri uri, LinkClickedListener linkClickedListener) {
+ this.uri = uri;
+ this.linkClickedListener = linkClickedListener;
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (linkClickedListener != null) {
+ linkClickedListener.onLink(uri);
+ }
+ }
+
+ public interface LinkClickedListener {
+ public void onLink(Uri uri);
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/DeviceListActivity.java b/src/org/thoughtcrime/securesms/DeviceListActivity.java
deleted file mode 100644
index effd2091b8..0000000000
--- a/src/org/thoughtcrime/securesms/DeviceListActivity.java
+++ /dev/null
@@ -1,215 +0,0 @@
-package org.thoughtcrime.securesms;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.v4.app.ListFragment;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.content.Loader;
-import android.support.v7.app.AlertDialog;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.ListView;
-import android.widget.Toast;
-
-import org.thoughtcrime.securesms.crypto.MasterSecret;
-import org.thoughtcrime.securesms.database.loaders.DeviceListLoader;
-import org.thoughtcrime.securesms.dependencies.InjectableType;
-import org.thoughtcrime.securesms.util.DynamicLanguage;
-import org.thoughtcrime.securesms.util.DynamicTheme;
-import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask;
-import org.thoughtcrime.securesms.util.TextSecurePreferences;
-import org.whispersystems.textsecure.api.TextSecureAccountManager;
-import org.whispersystems.textsecure.api.messages.multidevice.DeviceInfo;
-
-import java.io.IOException;
-import java.util.List;
-
-import javax.inject.Inject;
-
-public class DeviceListActivity extends PassphraseRequiredActionBarActivity {
-
-
- private final DynamicTheme dynamicTheme = new DynamicTheme();
- private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
-
- @Override
- public void onPreCreate() {
- dynamicTheme.onCreate(this);
- dynamicLanguage.onCreate(this);
- }
-
-
- @Override
- public void onCreate(Bundle bundle, @NonNull MasterSecret masterSecret) {
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- initFragment(android.R.id.content, new DeviceListFragment(), masterSecret);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- dynamicTheme.onResume(this);
- dynamicLanguage.onResume(this);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home: finish(); return true;
- }
-
- return false;
- }
-
- public static class DeviceListFragment extends ListFragment
- implements LoaderManager.LoaderCallbacks>, ListView.OnItemClickListener, InjectableType
- {
-
- private static final String TAG = DeviceListFragment.class.getSimpleName();
-
- @Inject TextSecureAccountManager accountManager;
-
- private View empty;
- private View progressContainer;
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- ApplicationContext.getInstance(activity).injectDependencies(this);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
- View view = inflater.inflate(R.layout.device_list_fragment, container, false);
-
- this.empty = view.findViewById(R.id.empty);
- this.progressContainer = view.findViewById(R.id.progress_container);
-
- return view;
- }
-
- @Override
- public void onActivityCreated(Bundle bundle) {
- super.onActivityCreated(bundle);
- getLoaderManager().initLoader(0, null, this).forceLoad();
- getListView().setOnItemClickListener(this);
- }
-
- @Override
- public Loader> onCreateLoader(int id, Bundle args) {
- empty.setVisibility(View.GONE);
- progressContainer.setVisibility(View.VISIBLE);
-
- return new DeviceListLoader(getActivity(), accountManager);
- }
-
- @Override
- public void onLoadFinished(Loader> loader, List data) {
- progressContainer.setVisibility(View.GONE);
-
- if (data == null) {
- handleLoaderFailed();
- return;
- }
-
- setListAdapter(new DeviceListAdapter(getActivity(), R.layout.device_list_item_view, data));
-
- if (data.isEmpty()) {
- empty.setVisibility(View.VISIBLE);
- TextSecurePreferences.setMultiDevice(getActivity(), false);
- } else {
- empty.setVisibility(View.GONE);
- }
- }
-
- @Override
- public void onLoaderReset(Loader> loader) {
- setListAdapter(null);
- }
-
- @Override
- public void onItemClick(AdapterView> parent, View view, int position, long id) {
- final String deviceName = ((DeviceListItem)view).getDeviceName();
- final long deviceId = ((DeviceListItem)view).getDeviceId();
-
- AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- builder.setTitle(getActivity().getString(R.string.DeviceListActivity_unlink_s, deviceName));
- builder.setMessage(R.string.DeviceListActivity_by_unlinking_this_device_it_will_no_longer_be_able_to_send_or_receive);
- builder.setNegativeButton(android.R.string.cancel, null);
- builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- handleDisconnectDevice(deviceId);
- }
- });
- builder.show();
- }
-
- private void handleLoaderFailed() {
- AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- builder.setMessage(R.string.DeviceListActivity_network_connection_failed);
- builder.setPositiveButton(R.string.DeviceListActivity_try_again,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- getLoaderManager().initLoader(0, null, DeviceListFragment.this);
- }
- });
- builder.show();
- }
-
- private void handleDisconnectDevice(final long deviceId) {
- new ProgressDialogAsyncTask(getActivity(),
- R.string.DeviceListActivity_unlinking_device_no_ellipsis,
- R.string.DeviceListActivity_unlinking_device)
- {
- @Override
- protected Void doInBackground(Void... params) {
- try {
- accountManager.removeDevice(deviceId);
- } catch (IOException e) {
- Log.w(TAG, e);
- Toast.makeText(getActivity(), R.string.DeviceListActivity_network_failed, Toast.LENGTH_LONG).show();
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(Void result) {
- super.onPostExecute(result);
- getLoaderManager().restartLoader(0, null, DeviceListFragment.this);
- }
- }.execute();
- }
-
- private static class DeviceListAdapter extends ArrayAdapter {
-
- private final int resource;
-
- public DeviceListAdapter(Context context, int resource, List objects) {
- super(context, resource, objects);
- this.resource = resource;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = ((Activity)getContext()).getLayoutInflater().inflate(resource, parent, false);
- }
-
- ((DeviceListItem)convertView).set(getItem(position));
-
- return convertView;
- }
- }
- }
-
-}
diff --git a/src/org/thoughtcrime/securesms/DeviceListFragment.java b/src/org/thoughtcrime/securesms/DeviceListFragment.java
new file mode 100644
index 0000000000..23d8b19eff
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/DeviceListFragment.java
@@ -0,0 +1,192 @@
+package org.thoughtcrime.securesms;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.support.v4.app.ListFragment;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.Loader;
+import android.support.v7.app.AlertDialog;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.Toast;
+
+import com.melnykov.fab.FloatingActionButton;
+
+import org.thoughtcrime.securesms.database.loaders.DeviceListLoader;
+import org.thoughtcrime.securesms.dependencies.InjectableType;
+import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask;
+import org.thoughtcrime.securesms.util.TextSecurePreferences;
+import org.thoughtcrime.securesms.util.ViewUtil;
+import org.whispersystems.textsecure.api.TextSecureAccountManager;
+import org.whispersystems.textsecure.api.messages.multidevice.DeviceInfo;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.inject.Inject;
+
+public class DeviceListFragment extends ListFragment
+ implements LoaderManager.LoaderCallbacks>,
+ ListView.OnItemClickListener, InjectableType, Button.OnClickListener
+{
+
+ private static final String TAG = DeviceListFragment.class.getSimpleName();
+
+ @Inject
+ TextSecureAccountManager accountManager;
+
+ private View empty;
+ private View progressContainer;
+ private FloatingActionButton addDeviceButton;
+ private Button.OnClickListener addDeviceButtonListener;
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ ApplicationContext.getInstance(activity).injectDependencies(this);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
+ View view = inflater.inflate(R.layout.device_list_fragment, container, false);
+
+ this.empty = view.findViewById(R.id.empty);
+ this.progressContainer = view.findViewById(R.id.progress_container);
+ this.addDeviceButton = ViewUtil.findById(view, R.id.add_device);
+ this.addDeviceButton.setOnClickListener(this);
+
+ return view;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle bundle) {
+ super.onActivityCreated(bundle);
+ getLoaderManager().initLoader(0, null, this).forceLoad();
+ getListView().setOnItemClickListener(this);
+ }
+
+ public void setAddDeviceButtonListener(Button.OnClickListener listener) {
+ this.addDeviceButtonListener = listener;
+ }
+
+ @Override
+ public Loader> onCreateLoader(int id, Bundle args) {
+ empty.setVisibility(View.GONE);
+ progressContainer.setVisibility(View.VISIBLE);
+
+ return new DeviceListLoader(getActivity(), accountManager);
+ }
+
+ @Override
+ public void onLoadFinished(Loader> loader, List data) {
+ progressContainer.setVisibility(View.GONE);
+
+ if (data == null) {
+ handleLoaderFailed();
+ return;
+ }
+
+ setListAdapter(new DeviceListAdapter(getActivity(), R.layout.device_list_item_view, data));
+
+ if (data.isEmpty()) {
+ empty.setVisibility(View.VISIBLE);
+ TextSecurePreferences.setMultiDevice(getActivity(), false);
+ } else {
+ empty.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader> loader) {
+ setListAdapter(null);
+ }
+
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ final String deviceName = ((DeviceListItem)view).getDeviceName();
+ final long deviceId = ((DeviceListItem)view).getDeviceId();
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setTitle(getActivity().getString(R.string.DeviceListActivity_unlink_s, deviceName));
+ builder.setMessage(R.string.DeviceListActivity_by_unlinking_this_device_it_will_no_longer_be_able_to_send_or_receive);
+ builder.setNegativeButton(android.R.string.cancel, null);
+ builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ handleDisconnectDevice(deviceId);
+ }
+ });
+ builder.show();
+ }
+
+ private void handleLoaderFailed() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setMessage(R.string.DeviceListActivity_network_connection_failed);
+ builder.setPositiveButton(R.string.DeviceListActivity_try_again,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ getLoaderManager().initLoader(0, null, DeviceListFragment.this);
+ }
+ });
+ builder.show();
+ }
+
+ private void handleDisconnectDevice(final long deviceId) {
+ new ProgressDialogAsyncTask(getActivity(),
+ R.string.DeviceListActivity_unlinking_device_no_ellipsis,
+ R.string.DeviceListActivity_unlinking_device)
+ {
+ @Override
+ protected Void doInBackground(Void... params) {
+ try {
+ accountManager.removeDevice(deviceId);
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ Toast.makeText(getActivity(), R.string.DeviceListActivity_network_failed, Toast.LENGTH_LONG).show();
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ super.onPostExecute(result);
+ getLoaderManager().restartLoader(0, null, DeviceListFragment.this);
+ }
+ }.execute();
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (addDeviceButtonListener != null) addDeviceButtonListener.onClick(v);
+ }
+
+ private static class DeviceListAdapter extends ArrayAdapter {
+
+ private final int resource;
+
+ public DeviceListAdapter(Context context, int resource, List objects) {
+ super(context, resource, objects);
+ this.resource = resource;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = ((Activity)getContext()).getLayoutInflater().inflate(resource, parent, false);
+ }
+
+ ((DeviceListItem)convertView).set(getItem(position));
+
+ return convertView;
+ }
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/components/ShapeScrim.java b/src/org/thoughtcrime/securesms/components/ShapeScrim.java
new file mode 100644
index 0000000000..20055c0599
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/components/ShapeScrim.java
@@ -0,0 +1,107 @@
+package org.thoughtcrime.securesms.components;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.View;
+
+import org.thoughtcrime.securesms.R;
+
+public class ShapeScrim extends View {
+
+ private enum ShapeType {
+ CIRCLE, SQUARE
+ }
+
+ private final Paint eraser;
+ private final ShapeType shape;
+ private final float radius;
+
+ private Bitmap scrim;
+ private Canvas scrimCanvas;
+
+ public ShapeScrim(Context context) {
+ this(context, null);
+ }
+
+ public ShapeScrim(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ShapeScrim(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
+ if (attrs != null) {
+ TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ShapeScrim, 0, 0);
+ String shapeName = typedArray.getString(R.styleable.ShapeScrim_shape);
+
+ if ("square".equalsIgnoreCase(shapeName)) this.shape = ShapeType.SQUARE;
+ else if ("circle".equalsIgnoreCase(shapeName)) this.shape = ShapeType.CIRCLE;
+ else this.shape = ShapeType.SQUARE;
+
+ this.radius = typedArray.getFloat(R.styleable.ShapeScrim_radius, 0.4f);
+
+ typedArray.recycle();
+ } else {
+ this.shape = ShapeType.SQUARE;
+ this.radius = 0.4f;
+ }
+
+ this.eraser = new Paint();
+ this.eraser.setColor(0xFFFFFFFF);
+ this.eraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ int shortDimension = getWidth() < getHeight() ? getWidth() : getHeight();
+ float drawRadius = shortDimension * radius;
+
+ if (scrimCanvas == null) {
+ scrim = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
+ scrimCanvas = new Canvas(scrim);
+ }
+
+ scrim.eraseColor(Color.TRANSPARENT);
+ scrimCanvas.drawColor(Color.parseColor("#55BDBDBD"));
+
+ if (shape == ShapeType.CIRCLE) drawCircle(scrimCanvas, drawRadius, eraser);
+ else drawSquare(scrimCanvas, drawRadius, eraser);
+
+ canvas.drawBitmap(scrim, 0, 0, null);
+ }
+
+ @Override
+ public void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
+ super.onSizeChanged(width, height, oldHeight, oldHeight);
+
+ if (width != oldWidth || height != oldHeight) {
+ scrim = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ scrimCanvas = new Canvas(scrim);
+ }
+ }
+
+ private void drawCircle(Canvas canvas, float radius, Paint eraser) {
+ canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, eraser);
+ }
+
+ private void drawSquare(Canvas canvas, float radius, Paint eraser) {
+ float left = (getWidth() / 2 ) - radius;
+ float top = (getHeight() / 2) - radius;
+ float right = left + (radius * 2);
+ float bottom = top + (radius * 2);
+
+ RectF square = new RectF(left, top, right, bottom);
+
+ canvas.drawRoundRect(square, 25, 25, eraser);
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/components/camera/CameraUtils.java b/src/org/thoughtcrime/securesms/components/camera/CameraUtils.java
index 028842d1ef..65ff7ae33d 100644
--- a/src/org/thoughtcrime/securesms/components/camera/CameraUtils.java
+++ b/src/org/thoughtcrime/securesms/components/camera/CameraUtils.java
@@ -7,6 +7,7 @@ import android.hardware.Camera.Size;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.DisplayMetrics;
+import android.util.Log;
import android.view.Surface;
import java.util.Collections;
@@ -22,6 +23,7 @@ public class CameraUtils {
int width,
int height,
@NonNull Camera camera) {
+ Log.w("CameraUtils", String.format("getPreferredPreviewSize(%d, %d, %d)", displayOrientation, width, height));
double targetRatio = (double)width / height;
Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
diff --git a/src/org/thoughtcrime/securesms/components/camera/CameraView.java b/src/org/thoughtcrime/securesms/components/camera/CameraView.java
index 95ed524e14..32bc036987 100644
--- a/src/org/thoughtcrime/securesms/components/camera/CameraView.java
+++ b/src/org/thoughtcrime/securesms/components/camera/CameraView.java
@@ -19,6 +19,7 @@ import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.pm.ActivityInfo;
+import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Rect;
import android.hardware.Camera;
@@ -39,6 +40,7 @@ import java.io.IOException;
import java.util.List;
import org.thoughtcrime.securesms.ApplicationContext;
+import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
@@ -73,7 +75,15 @@ public class CameraView extends FrameLayout {
super(context, attrs, defStyle);
setBackgroundColor(Color.BLACK);
- if (isMultiCamera()) cameraId = TextSecurePreferences.getDirectCaptureCameraId(context);
+ if (attrs != null) {
+ TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CameraView);
+ int camera = typedArray.getInt(R.styleable.CameraView_camera, -1);
+
+ if (camera != -1) cameraId = camera;
+ else if (isMultiCamera()) cameraId = TextSecurePreferences.getDirectCaptureCameraId(context);
+
+ typedArray.recycle();
+ }
surface = new CameraSurfaceView(getContext());
onOrientationChange = new OnOrientationChange(context.getApplicationContext());
@@ -87,7 +97,9 @@ public class CameraView extends FrameLayout {
Log.w(TAG, "onResume() queued");
enqueueTask(new SerialAsyncTask() {
@Override
- protected @Nullable Camera onRunBackground() {
+ protected
+ @Nullable
+ Camera onRunBackground() {
try {
return Camera.open(cameraId);
} catch (Exception e) {
@@ -131,15 +143,19 @@ public class CameraView extends FrameLayout {
enqueueTask(new SerialAsyncTask() {
private Optional cameraToDestroy;
- @Override protected void onPreMain() {
+
+ @Override
+ protected void onPreMain() {
cameraToDestroy = camera;
camera = Optional.absent();
}
- @Override protected Void onRunBackground() {
+ @Override
+ protected Void onRunBackground() {
if (cameraToDestroy.isPresent()) {
try {
stopPreview();
+ cameraToDestroy.get().setPreviewCallback(null);
cameraToDestroy.get().release();
Log.w(TAG, "released old camera instance");
} catch (Exception e) {
@@ -224,6 +240,30 @@ public class CameraView extends FrameLayout {
this.listener = listener;
}
+ public void setPreviewCallback(final PreviewCallback previewCallback) {
+ enqueueTask(new PostInitializationTask() {
+ @Override
+ protected void onPostMain(Void avoid) {
+ if (camera.isPresent()) {
+ camera.get().setPreviewCallback(new Camera.PreviewCallback() {
+ @Override
+ public void onPreviewFrame(byte[] data, Camera camera) {
+ if (!CameraView.this.camera.isPresent()) {
+ return;
+ }
+
+ final int rotation = getCameraPictureOrientation();
+ final Size previewSize = camera.getParameters().getPreviewSize();
+ if (data != null) {
+ previewCallback.onPreviewFrame(new PreviewFrame(data, previewSize.width, previewSize.height, rotation));
+ }
+ }
+ });
+ }
+ }
+ });
+ }
+
public boolean isMultiCamera() {
return Camera.getNumberOfCameras() > 1;
}
@@ -515,4 +555,38 @@ public class CameraView extends FrameLayout {
void onImageCapture(@NonNull final byte[] imageBytes);
void onCameraFail();
}
+
+ public interface PreviewCallback {
+ void onPreviewFrame(@NonNull PreviewFrame frame);
+ }
+
+ public static class PreviewFrame {
+ private final @NonNull byte[] data;
+ private final int width;
+ private final int height;
+ private final int orientation;
+
+ private PreviewFrame(@NonNull byte[] data, int width, int height, int orientation) {
+ this.data = data;
+ this.width = width;
+ this.height = height;
+ this.orientation = orientation;
+ }
+
+ public @NonNull byte[] getData() {
+ return data;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ public int getOrientation() {
+ return orientation;
+ }
+ }
}
diff --git a/src/org/thoughtcrime/securesms/dependencies/TextSecureCommunicationModule.java b/src/org/thoughtcrime/securesms/dependencies/TextSecureCommunicationModule.java
index 0d999b5728..2a38e2b3a9 100644
--- a/src/org/thoughtcrime/securesms/dependencies/TextSecureCommunicationModule.java
+++ b/src/org/thoughtcrime/securesms/dependencies/TextSecureCommunicationModule.java
@@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.dependencies;
import android.content.Context;
import org.thoughtcrime.securesms.BuildConfig;
-import org.thoughtcrime.securesms.DeviceListActivity;
+import org.thoughtcrime.securesms.DeviceListFragment;
import org.thoughtcrime.securesms.crypto.storage.TextSecureAxolotlStore;
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
import org.thoughtcrime.securesms.jobs.CleanPreKeysJob;
@@ -43,7 +43,7 @@ import dagger.Provides;
PushNotificationReceiveJob.class,
MultiDeviceContactUpdateJob.class,
MultiDeviceGroupUpdateJob.class,
- DeviceListActivity.DeviceListFragment.class,
+ DeviceListFragment.class,
RefreshAttributesJob.class,
GcmRefreshJob.class})
public class TextSecureCommunicationModule {