From 852634b2946e841fa986b76eb55ffe81a1926150 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Wed, 24 Aug 2016 18:51:45 -0700 Subject: [PATCH] Support for updated fingerprint format // FREEBIE --- AndroidManifest.xml | 7 - res/drawable-hdpi/ic_check_white_48dp.png | Bin 0 -> 276 bytes res/drawable-hdpi/ic_close_white_48dp.png | Bin 0 -> 347 bytes res/drawable-mdpi/ic_check_white_48dp.png | Bin 0 -> 199 bytes res/drawable-mdpi/ic_close_white_48dp.png | Bin 0 -> 257 bytes res/drawable-xhdpi/ic_check_white_48dp.png | Bin 0 -> 308 bytes res/drawable-xhdpi/ic_close_white_48dp.png | Bin 0 -> 436 bytes res/drawable-xxhdpi/ic_check_white_48dp.png | Bin 0 -> 386 bytes res/drawable-xxhdpi/ic_close_white_48dp.png | Bin 0 -> 524 bytes res/drawable-xxxhdpi/ic_check_white_48dp.png | Bin 0 -> 466 bytes res/drawable-xxxhdpi/ic_close_white_48dp.png | Bin 0 -> 707 bytes res/drawable/qr_code_background.xml | 5 + res/layout/verify_display_fragment.xml | 151 +++++++ res/layout/verify_identity_activity.xml | 49 --- res/layout/verify_scan_fragment.xml | 27 ++ res/layout/view_identity_activity.xml | 23 -- res/values/attrs.xml | 2 + res/values/strings.xml | 22 +- res/values/styles.xml | 5 + res/values/themes.xml | 4 + res/xml/recipient_preferences.xml | 3 +- .../securesms/ConfirmIdentityDialog.java | 4 +- .../securesms/DeviceActivity.java | 6 +- .../securesms/DeviceAddFragment.java | 112 +---- .../securesms/KeyScanningActivity.java | 136 ------ .../RecipientPreferenceActivity.java | 79 +++- .../securesms/VerifyIdentityActivity.java | 388 +++++++++++++----- .../securesms/ViewIdentityActivity.java | 117 ------ .../securesms/ViewLocalIdentityActivity.java | 66 --- .../securesms/components/SquareImageView.java | 31 ++ src/org/thoughtcrime/securesms/qr/QrCode.java | 37 ++ .../securesms/qr/ScanListener.java | 5 + .../securesms/qr/ScanningThread.java | 125 ++++++ 33 files changed, 788 insertions(+), 616 deletions(-) create mode 100644 res/drawable-hdpi/ic_check_white_48dp.png create mode 100644 res/drawable-hdpi/ic_close_white_48dp.png create mode 100644 res/drawable-mdpi/ic_check_white_48dp.png create mode 100644 res/drawable-mdpi/ic_close_white_48dp.png create mode 100644 res/drawable-xhdpi/ic_check_white_48dp.png create mode 100644 res/drawable-xhdpi/ic_close_white_48dp.png create mode 100644 res/drawable-xxhdpi/ic_check_white_48dp.png create mode 100644 res/drawable-xxhdpi/ic_close_white_48dp.png create mode 100644 res/drawable-xxxhdpi/ic_check_white_48dp.png create mode 100644 res/drawable-xxxhdpi/ic_close_white_48dp.png create mode 100644 res/drawable/qr_code_background.xml create mode 100644 res/layout/verify_display_fragment.xml delete mode 100644 res/layout/verify_identity_activity.xml create mode 100644 res/layout/verify_scan_fragment.xml delete mode 100644 res/layout/view_identity_activity.xml delete mode 100644 src/org/thoughtcrime/securesms/KeyScanningActivity.java delete mode 100644 src/org/thoughtcrime/securesms/ViewIdentityActivity.java delete mode 100644 src/org/thoughtcrime/securesms/ViewLocalIdentityActivity.java create mode 100644 src/org/thoughtcrime/securesms/components/SquareImageView.java create mode 100644 src/org/thoughtcrime/securesms/qr/QrCode.java create mode 100644 src/org/thoughtcrime/securesms/qr/ScanListener.java create mode 100644 src/org/thoughtcrime/securesms/qr/ScanningThread.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index f663d0f592..2703174659 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -262,13 +262,6 @@ android:windowSoftInputMode="stateHidden" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> - - - - diff --git a/res/drawable-hdpi/ic_check_white_48dp.png b/res/drawable-hdpi/ic_check_white_48dp.png new file mode 100644 index 0000000000000000000000000000000000000000..2c2ad771f72c8beaa5adbff66526893d8767990d GIT binary patch literal 276 zcmV+v0qg#WP)i*jo}i373|mq*}I4NcaPr1vPBqUj4{So^{}zU z+2V@M76*V4(wk$0gq_FHCw*~rLF6PjJvrp8;xy!tvzjxILyidcxS;vzge%E8;0~bA zCza(K@t8(4No6@FJiXx|O)AO}!Fe{GAi-fQe6!HylPYi`B&W#9B~|3)kkD4;1WDtE zgcdi2M1XboCryi6C~+>FcwG}oCT5=CqmNX1W4MP8OZ|2BU#~0BugB|7-NjF ae|iA|xq+-YC!0F}0000 literal 0 HcmV?d00001 diff --git a/res/drawable-hdpi/ic_close_white_48dp.png b/res/drawable-hdpi/ic_close_white_48dp.png new file mode 100644 index 0000000000000000000000000000000000000000..6b717e0dda8649aa3b5f1d6851ba0dd20cc4ea66 GIT binary patch literal 347 zcmV-h0i^zkP)vMkH8EXyh{JTRaptiwcq@D(P;=ehLbl?EMKm*m7(@7_siS}}k zNFtnck{HL4mc!ypcyUoqJV~4rM^fS3C#iAnkyJU3biCmDy`VZLOv=LXld^HVq zOA5m<3jYBNi%A&<@ZO?$P9};P4Y5CjG5$M&YXI45J{s}~# zf|&?x1_gn4B7+hS@X!l}&!voFhmZP^sujifL@~PKMMM~{6xH}^g$q7WOzwCQ5vHTU z6`v~H@rlA8e;CUh_(b84zg=+ih`wG<)HiJjzSlQx5#CnjMR;A)R^jtaTa9;7rSy)7O%~`cm?ZjXImW?6TYRT<;U^@VKiSj`soFk00000NkvXX Hu0mjfhD&W| literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi/ic_check_white_48dp.png b/res/drawable-xhdpi/ic_check_white_48dp.png new file mode 100644 index 0000000000000000000000000000000000000000..d670618c7e96225f7756cb4c2743e7ebbf688cf8 GIT binary patch literal 308 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD0wg^q?%xcgetWt&hE&{od;P3vlYxj!B3Hnh z1714EonD+``t$ZU)3IJ|2aboi6~%ilJM7!-={Kv*@R&q$518Ox)_m^XpXp1^&DOu% zB6r^~@kqJ%j0e`LXFkL-JviL|Ir_(khlktwLa#r83bn@|VwIw^gKjzH(>hUq>tlAsh9~)aj{(R+p z`Qx=v&?j!r?}<)Pb+Jxa|B9Td>MRd7#ru4CATBh=u6rtH32ETvhYyB39D^lU%DgQgv&#U%8l^-CA%qY@2qA=!B5=cm zUgehujQG*l{{`@rPr!f|fEk^>KI9WteE@ilV6HEl&_rJ@p_zU*;T}RirIc{5NocNLm*7JGdV(AM zYYDFOvk5~8{Z*t_>U=!XvoehUSEh=adIga45oe)B9kGgT}7UT3Cirmr(oHPv^YQ1-p=Hlh5u;xggf zX{&yw+El-OrrKQJRl@b7x{HLmNkj95`a#LLnW{Veb2C+!`ppt#r)=g4@?c8RY{yJj~W@W|h^mU4q`i)2y~Rw@J`jIh$1%|In!~{Tb{nMqaxlgb+dq eA%qY@zJ@mriVM?qfwL0;0000%}Q; zF2S4}H|$TDd2vZpm**~-Yq`B?`RDI>=AM&O5Gd%|E$QZt^z5~{GD`E>Rvd3$aT*N; zOLOqc*;IUZuv6kZgmJK$9l@9vVsjrT2@;$4f9Aj2?<$n$rI*_Xo)20iV{^LW(+3^J zQ%8;GYRI48%s#IpUFzc0%6q#n%ePj>ecry+zuL2m&h5)FJEp}G0XUN2K_Q242j*Cv&FuPo>v~sC%QnFDLoHv2P p-=jrc<&dz@!-+`2ap}rGh6f+*rTN0;Q-ER1;OXk;vd$@?2>?Pppu7M8 literal 0 HcmV?d00001 diff --git a/res/drawable-xxhdpi/ic_close_white_48dp.png b/res/drawable-xxhdpi/ic_close_white_48dp.png new file mode 100644 index 0000000000000000000000000000000000000000..4927bc242e23be272c9fd4be0f4da56a0e1c54d6 GIT binary patch literal 524 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=v;urWT!Hj|;y~N(Q~E&Fyd^u<8%L#D-{7p<|%4@n9yOn_`BhIN5Se>F5&yX=g3#NJr`MF8+b%+$B8BmnCIPndHJicwnxNiHsOkk&+*iSjJjmJe1$X)E`JjJT7)$8wm+rVMFtHWj=GUBk= z=ke_WBcq(Ni(Gt*T>KFvWMaekQTMd-+4Z${JvAkNz8{`iZ@=$N<$){y-?y+S-FBWF zc%ND6E)u%gY+Qf)aIpRD!%Emtu>HZuoi`qLflzYyjmJ==_n}6?N_~C6*PuJQ&feR6 zYOCY7g5y!UB2LPQOzopr0M`r!;Q#;t literal 0 HcmV?d00001 diff --git a/res/drawable-xxxhdpi/ic_check_white_48dp.png b/res/drawable-xxxhdpi/ic_check_white_48dp.png new file mode 100644 index 0000000000000000000000000000000000000000..23a197082a47c660dad770daeba573a95d75ea2b GIT binary patch literal 466 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE1xWt5x}=AJfpMm%i(^Q|t+zJev{39&QE{TPGE8vem-@t`#CYb?#cA|M}O{rGCjWT=Tme-9-#=xd_M1wJBph6 zqldqLHfI01`_J^5KXcV@RQ=0TpI|@N;a>^U&&!-o{82eKKAcbY< z2ajj^864kPWsxp?V!ofke`VpCQ~xi4+4WY9e_pbDT8_rq|BvPRe^f(Z*1hI@(yu<@ nzpENd0MmYAW#m!9m7$`@oJXx|Zh_XZ*B}W`S3j3^P6< literal 0 HcmV?d00001 diff --git a/res/drawable-xxxhdpi/ic_close_white_48dp.png b/res/drawable-xxxhdpi/ic_close_white_48dp.png new file mode 100644 index 0000000000000000000000000000000000000000..1ab2312754a15f2f679d1f0361cab0aa9954ce81 GIT binary patch literal 707 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE8Azrw%`pX1#sNMdu0Z-fiD3KGM@>L&0wqCy z!3?}ShmRdO`suT!jkVQ|?c1(&>uh9TVAA(=aSW-5dwb`!pLC!ATVf7V-(l6d|I2$f zz2Z#?z8oAmWoi9?(MLwjedmJbUyI6KyA_4_e{cQy*L)8?71|&D|M_E0?8pBVcit`k zAs<|@=KseXd*?r#-_-m!c*83=#rpq#)Arc^P6vN1PdK|jz~GetYnr z-~)dbF#K5F@cTQrUHjiy#_#9rR^AkKrj z?G3;0GQBJ3-f>@h$DAk6cQ-IS5a(*RueG3Vf5Wf8Os~Fkuec9Xw!h)`U#558xp&m> zfAH1&((6{Z9Ew5&XTg2_34i~@+aAytao8W<@VAcf`)}4e@5KxD#~(oE82n@UCHBbv z$X}_q{F3!kUL>wNp82qKS-98prT4zu+b?FiU!S_XxW?W#{>ksi#`o#0`{s-Os0(|5 z#0h%v`(|bEeSiPwXc8!@A`*W8yeW43Ja-9N7$616{`e0c*VZq6@c2W+_uDhWC*OJJ zf1CC3bJhd=T*uCHhyC5W{q{Fj&&T(*ZtPu@^(UKk_4JnB`i%)6XKnkqUhGZm6!U*~ ro9?dWdLJwH(?0(CO^n!HdtUzRDW+vbYyai|lNE!ftDnm{r-UW|G*Ff) literal 0 HcmV?d00001 diff --git a/res/drawable/qr_code_background.xml b/res/drawable/qr_code_background.xml new file mode 100644 index 0000000000..a5266a706e --- /dev/null +++ b/res/drawable/qr_code_background.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/res/layout/verify_display_fragment.xml b/res/layout/verify_display_fragment.xml new file mode 100644 index 0000000000..633756b39d --- /dev/null +++ b/res/layout/verify_display_fragment.xml @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/verify_identity_activity.xml b/res/layout/verify_identity_activity.xml deleted file mode 100644 index 78146b758c..0000000000 --- a/res/layout/verify_identity_activity.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/layout/verify_scan_fragment.xml b/res/layout/verify_scan_fragment.xml new file mode 100644 index 0000000000..d379c60023 --- /dev/null +++ b/res/layout/verify_scan_fragment.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + diff --git a/res/layout/view_identity_activity.xml b/res/layout/view_identity_activity.xml deleted file mode 100644 index 1bc52c3ac2..0000000000 --- a/res/layout/view_identity_activity.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - diff --git a/res/values/attrs.xml b/res/values/attrs.xml index e6780cdfeb..907033bdeb 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -129,6 +129,8 @@ + + diff --git a/res/values/strings.xml b/res/values/strings.xml index e5023275ae..3d277d300c 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -554,16 +554,10 @@ Disappearing message time set to %s - You do not have an identity key. - Recipient has no identity key. - Recipient has no identity key! - Scan contact\'s QR code - Display your QR code - WARNING, the scanned key DOES NOT match! Please check the fingerprint text carefully. - NOT Verified! - Their key is correct. It is also necessary to verify your key with them as well. - Verified! - You don\'t have an identity key! + Your contact is running an old version of Signal, please ask them to update before verifying security numbers. + You\'re attempting to verify security numbers with %1$s, but scanned %2$s instead. + The scanned QR code is not a correctly formatted security number verification code. Please try scanning again. + You do not have an identity key. @@ -935,10 +929,10 @@ Enter a name or number Add member - - Their identity (they read): - Your identity (you read): - + + Scan the code on your contact\'s phone, or ask them to scan your code, to verify that your messages are end-to-end encrypted. You can alternately compare the number above. + Tap to scan + Some issues need your attention. Sent diff --git a/res/values/styles.xml b/res/values/styles.xml index 029e17535a..3b466a9c72 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -227,5 +227,10 @@ @null + diff --git a/res/values/themes.xml b/res/values/themes.xml index d816fcae1b..6f02a51683 100644 --- a/res/values/themes.xml +++ b/res/values/themes.xml @@ -127,6 +127,8 @@ @color/gray65 @color/black + @color/gray5 + @color/gray12 #66555555 #44555555 @@ -235,6 +237,8 @@ #BFffffff @drawable/conversation_item_sent_indicator_text_shape_dark + #ff333333 + @drawable/ic_info_outline_dark @drawable/ic_warning_dark diff --git a/res/xml/recipient_preferences.xml b/res/xml/recipient_preferences.xml index ee9e38ff14..5185e1fcc1 100644 --- a/res/xml/recipient_preferences.xml +++ b/res/xml/recipient_preferences.xml @@ -38,7 +38,8 @@ + android:persistent="false" + android:enabled="false"/> = Build.VERSION_CODES.LOLLIPOP) { diff --git a/src/org/thoughtcrime/securesms/DeviceAddFragment.java b/src/org/thoughtcrime/securesms/DeviceAddFragment.java index a4de3916b2..4090e82bbb 100644 --- a/src/org/thoughtcrime/securesms/DeviceAddFragment.java +++ b/src/org/thoughtcrime/securesms/DeviceAddFragment.java @@ -31,20 +31,17 @@ 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.qr.ScanListener; +import org.thoughtcrime.securesms.qr.ScanningThread; 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(); +public class DeviceAddFragment extends Fragment { private ViewGroup container; private LinearLayout overlay; private ImageView devicesImage; private CameraView scannerView; - private PreviewFrame previewFrame; private ScanningThread scanningThread; private ScanListener scanListener; @@ -54,8 +51,6 @@ public class DeviceAddFragment extends Fragment implements PreviewCallback { 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); @@ -86,10 +81,10 @@ public class DeviceAddFragment extends Fragment implements PreviewCallback { @Override public void onResume() { super.onResume(); - this.scannerView.onResume(); - this.scannerView.setPreviewCallback(this); - this.previewFrame = null; this.scanningThread = new ScanningThread(); + this.scanningThread.setScanListener(scanListener); + this.scannerView.onResume(); + this.scannerView.setPreviewCallback(scanningThread); this.scanningThread.start(); } @@ -113,24 +108,9 @@ public class DeviceAddFragment extends Fragment implements PreviewCallback { } this.scannerView.onResume(); - this.scannerView.setPreviewCallback(this); + this.scannerView.setPreviewCallback(scanningThread); } - @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; @@ -138,83 +118,11 @@ public class DeviceAddFragment extends Fragment implements PreviewCallback { 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; + if (this.scanningThread != null) { + this.scanningThread.setScanListener(scanListener); } } - public interface ScanListener { - public void onUrlFound(Uri uri); - } + } diff --git a/src/org/thoughtcrime/securesms/KeyScanningActivity.java b/src/org/thoughtcrime/securesms/KeyScanningActivity.java deleted file mode 100644 index 234e558298..0000000000 --- a/src/org/thoughtcrime/securesms/KeyScanningActivity.java +++ /dev/null @@ -1,136 +0,0 @@ -/** - * Copyright (C) 2011 Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.thoughtcrime.securesms; - -import android.content.Intent; -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.widget.Toast; - -import com.google.zxing.integration.android.IntentIntegrator; -import com.google.zxing.integration.android.IntentResult; - -import org.thoughtcrime.securesms.crypto.MasterSecret; -import org.thoughtcrime.securesms.util.Base64; -import org.thoughtcrime.securesms.util.Dialogs; -import org.thoughtcrime.securesms.util.DynamicLanguage; -import org.thoughtcrime.securesms.util.DynamicTheme; -import org.whispersystems.libsignal.IdentityKey; - -/** - * Activity for initiating/receiving key QR code scans. - * - * @author Moxie Marlinspike - */ -public abstract class KeyScanningActivity extends PassphraseRequiredActionBarActivity { - - private final DynamicTheme dynamicTheme = new DynamicTheme(); - private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); - - @Override - protected void onPreCreate() { - dynamicTheme.onCreate(this); - dynamicLanguage.onCreate(this); - } - - @Override - public void onResume() { - super.onResume(); - dynamicTheme.onResume(this); - dynamicLanguage.onResume(this); - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - super.onPrepareOptionsMenu(menu); - - MenuInflater inflater = this.getMenuInflater(); - menu.clear(); - - inflater.inflate(R.menu.key_scanning, menu); - - menu.findItem(R.id.menu_scan).setTitle(getScanString()); - menu.findItem(R.id.menu_get_scanned).setTitle(getDisplayString()); - - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - super.onOptionsItemSelected(item); - - switch (item.getItemId()) { - case R.id.menu_scan: initiateScan(); return true; - case R.id.menu_get_scanned: initiateDisplay(); return true; - case android.R.id.home: finish(); return true; - } - - return false; - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent intent) { - IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent); - - if ((scanResult != null) && (scanResult.getContents() != null)) { - String data = scanResult.getContents(); - - if (data.equals(Base64.encodeBytes(getIdentityKeyToCompare().serialize()))) { - Dialogs.showInfoDialog(this, getVerifiedTitle(), getVerifiedMessage()); - } else { - Dialogs.showAlertDialog(this, getNotVerifiedTitle(), getNotVerifiedMessage()); - } - } else { - Toast.makeText(this, R.string.KeyScanningActivity_no_scanned_key_found_exclamation, - Toast.LENGTH_LONG).show(); - } - } - - private IntentIntegrator getIntentIntegrator() { - IntentIntegrator intentIntegrator = new IntentIntegrator(this); - intentIntegrator.setButtonYesByID(R.string.yes); - intentIntegrator.setButtonNoByID(R.string.no); - intentIntegrator.setTitleByID(R.string.KeyScanningActivity_install_barcode_Scanner); - intentIntegrator.setMessageByID(R.string.KeyScanningActivity_this_application_requires_barcode_scanner_would_you_like_to_install_it); - return intentIntegrator; - } - - protected void initiateScan() { - IntentIntegrator intentIntegrator = getIntentIntegrator(); - intentIntegrator.initiateScan(); - } - - protected void initiateDisplay() { - IntentIntegrator intentIntegrator = getIntentIntegrator(); - intentIntegrator.shareText(Base64.encodeBytes(getIdentityKeyToDisplay().serialize())); - } - - protected abstract String getScanString(); - protected abstract String getDisplayString(); - - protected abstract String getNotVerifiedTitle(); - protected abstract String getNotVerifiedMessage(); - - protected abstract IdentityKey getIdentityKeyToCompare(); - protected abstract IdentityKey getIdentityKeyToDisplay(); - - protected abstract String getVerifiedTitle(); - protected abstract String getVerifiedMessage(); - -} diff --git a/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java b/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java index 863df016ae..53a948e393 100644 --- a/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java +++ b/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java @@ -29,7 +29,9 @@ import android.widget.TextView; import org.thoughtcrime.securesms.color.MaterialColor; import org.thoughtcrime.securesms.color.MaterialColors; import org.thoughtcrime.securesms.components.AvatarImageView; +import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable; import org.thoughtcrime.securesms.crypto.MasterSecret; +import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.VibrateState; @@ -37,11 +39,22 @@ import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob; import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob; import org.thoughtcrime.securesms.preferences.AdvancedRingtonePreference; import org.thoughtcrime.securesms.preferences.ColorPreference; +import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; import org.thoughtcrime.securesms.util.DynamicTheme; +import org.thoughtcrime.securesms.util.concurrent.ListenableFuture; +import org.thoughtcrime.securesms.util.concurrent.SettableFuture; +import org.whispersystems.libsignal.IdentityKey; +import org.whispersystems.libsignal.SignalProtocolAddress; +import org.whispersystems.libsignal.state.SessionRecord; +import org.whispersystems.libsignal.state.SessionStore; +import org.whispersystems.libsignal.util.guava.Optional; +import org.whispersystems.signalservice.api.push.SignalServiceAddress; + +import java.util.concurrent.ExecutionException; public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActivity implements Recipients.RecipientsModifiedListener { @@ -179,8 +192,9 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi private final Handler handler = new Handler(); - private Recipients recipients; + private Recipients recipients; private BroadcastReceiver staleReceiver; + private MasterSecret masterSecret; @Override public void onCreate(Bundle icicle) { @@ -189,6 +203,8 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi addPreferencesFromResource(R.xml.recipient_preferences); initializeRecipients(); + this.masterSecret = getArguments().getParcelable("master_secret"); + this.findPreference(PREFERENCE_TONE) .setOnPreferenceChangeListener(new RingtoneChangeListener()); this.findPreference(PREFERENCE_VIBRATE) @@ -199,8 +215,6 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi .setOnPreferenceClickListener(new BlockClickedListener()); this.findPreference(PREFERENCE_COLOR) .setOnPreferenceChangeListener(new ColorChangeListener()); - this.findPreference(PREFERENCE_IDENTITY) - .setOnPreferenceClickListener(new IdentityClickedListener()); } @Override @@ -245,7 +259,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi ListPreference vibratePreference = (ListPreference) this.findPreference(PREFERENCE_VIBRATE); ColorPreference colorPreference = (ColorPreference) this.findPreference(PREFERENCE_COLOR); Preference blockPreference = this.findPreference(PREFERENCE_BLOCK); - Preference identityPreference = this.findPreference(PREFERENCE_IDENTITY); + final Preference identityPreference = this.findPreference(PREFERENCE_IDENTITY); mutePreference.setChecked(recipients.isMuted()); @@ -281,6 +295,23 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi if (recipients.isBlocked()) blockPreference.setTitle(R.string.RecipientPreferenceActivity_unblock); else blockPreference.setTitle(R.string.RecipientPreferenceActivity_block); + + getRemoteIdentityKey(getActivity(), masterSecret, recipients.getPrimaryRecipient()).addListener(new ListenableFuture.Listener>() { + @Override + public void onSuccess(Optional result) { + if (result.isPresent()) { + identityPreference.setOnPreferenceClickListener(new IdentityClickedListener(result.get())); + identityPreference.setEnabled(true); + } else { + getPreferenceScreen().removePreference(identityPreference); + } + } + + @Override + public void onFailure(ExecutionException e) { + getPreferenceScreen().removePreference(identityPreference); + } + }); } } @@ -294,6 +325,36 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi }); } + private ListenableFuture> getRemoteIdentityKey(final Context context, + final MasterSecret masterSecret, + final Recipient recipient) + { + final SettableFuture> future = new SettableFuture<>(); + + new AsyncTask>() { + @Override + protected Optional doInBackground(Recipient... recipient) { + SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret); + SignalProtocolAddress axolotlAddress = new SignalProtocolAddress(recipient[0].getNumber(), SignalServiceAddress.DEFAULT_DEVICE_ID); + SessionRecord record = sessionStore.loadSession(axolotlAddress); + + if (record == null) { + return Optional.absent(); + } + + return Optional.fromNullable(record.getSessionState().getRemoteIdentityKey()); + } + + @Override + protected void onPostExecute(Optional result) { + future.set(result); + } + }.execute(recipient); + + return future; + } + + private class RingtoneChangeListener implements Preference.OnPreferenceChangeListener { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { @@ -413,10 +474,18 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi } private class IdentityClickedListener implements Preference.OnPreferenceClickListener { + + private final IdentityKey identityKey; + + private IdentityClickedListener(IdentityKey identityKey) { + this.identityKey = identityKey; + } + @Override public boolean onPreferenceClick(Preference preference) { Intent verifyIdentityIntent = new Intent(getActivity(), VerifyIdentityActivity.class); - verifyIdentityIntent.putExtra("recipient", recipients.getPrimaryRecipient().getRecipientId()); + verifyIdentityIntent.putExtra(VerifyIdentityActivity.RECIPIENT_ID, recipients.getPrimaryRecipient().getRecipientId()); + verifyIdentityIntent.putExtra(VerifyIdentityActivity.RECIPIENT_IDENTITY, new IdentityKeyParcelable(identityKey)); startActivity(verifyIdentityIntent); return true; diff --git a/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java b/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java index 7bc3fe22e0..935d02d4da 100644 --- a/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java +++ b/src/org/thoughtcrime/securesms/VerifyIdentityActivity.java @@ -1,6 +1,5 @@ /** - * Copyright (C) 2011 Whisper Systems - * Copyright (C) 2013 Open Whisper Systems + * Copyright (C) 2016 Open Whisper Systems * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,155 +16,360 @@ */ package org.thoughtcrime.securesms; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.PorterDuff; +import android.graphics.drawable.BitmapDrawable; import android.os.Bundle; +import android.os.Vibrator; +import android.support.annotation.DrawableRes; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentTransaction; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.view.animation.AnticipateInterpolator; +import android.view.animation.OvershootInterpolator; +import android.view.animation.ScaleAnimation; +import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; +import org.thoughtcrime.securesms.components.camera.CameraView; import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.MasterSecret; -import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore; +import org.thoughtcrime.securesms.qr.QrCode; +import org.thoughtcrime.securesms.qr.ScanListener; +import org.thoughtcrime.securesms.qr.ScanningThread; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientFactory; -import org.thoughtcrime.securesms.util.Hex; -import org.whispersystems.libsignal.SignalProtocolAddress; +import org.thoughtcrime.securesms.util.DynamicLanguage; +import org.thoughtcrime.securesms.util.DynamicTheme; +import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.thoughtcrime.securesms.util.Util; +import org.thoughtcrime.securesms.util.ViewUtil; import org.whispersystems.libsignal.IdentityKey; -import org.whispersystems.libsignal.state.SessionRecord; -import org.whispersystems.libsignal.state.SessionStore; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; +import org.whispersystems.libsignal.fingerprint.Fingerprint; +import org.whispersystems.libsignal.fingerprint.FingerprintIdentifierMismatchException; +import org.whispersystems.libsignal.fingerprint.FingerprintParsingException; +import org.whispersystems.libsignal.fingerprint.FingerprintVersionMismatchException; +import org.whispersystems.libsignal.fingerprint.NumericFingerprintGenerator; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; /** * Activity for verifying identity keys. * * @author Moxie Marlinspike */ -public class VerifyIdentityActivity extends KeyScanningActivity { +public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity implements ScanListener, View.OnClickListener { + + private static final String TAG = VerifyIdentityActivity.class.getSimpleName(); - private Recipient recipient; - private MasterSecret masterSecret; + public static final String RECIPIENT_ID = "recipient_id"; + public static final String RECIPIENT_IDENTITY = "recipient_identity"; - private TextView localIdentityFingerprint; - private TextView remoteIdentityFingerprint; + private final DynamicTheme dynamicTheme = new DynamicTheme(); + private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); + + private VerifyDisplayFragment displayFragment = new VerifyDisplayFragment(); + private VerifyScanFragment scanFragment = new VerifyScanFragment(); + + @Override + public void onPreCreate() { + dynamicTheme.onCreate(this); + dynamicLanguage.onCreate(this); + } @Override protected void onCreate(Bundle state, @NonNull MasterSecret masterSecret) { - this.masterSecret = masterSecret; getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setTitle(R.string.AndroidManifest__verify_identity); - setContentView(R.layout.verify_identity_activity); + Recipient recipient = RecipientFactory.getRecipientForId(this, getIntent().getLongExtra(RECIPIENT_ID, -1), true); + + Bundle extras = new Bundle(); + extras.putParcelable(VerifyDisplayFragment.REMOTE_IDENTITY, getIntent().getParcelableExtra(RECIPIENT_IDENTITY)); + extras.putString(VerifyDisplayFragment.REMOTE_NUMBER, recipient.getNumber()); + extras.putParcelable(VerifyDisplayFragment.LOCAL_IDENTITY, new IdentityKeyParcelable(IdentityKeyUtil.getIdentityKey(this))); + extras.putString(VerifyDisplayFragment.LOCAL_NUMBER, TextSecurePreferences.getLocalNumber(this)); - this.localIdentityFingerprint = (TextView)findViewById(R.id.you_read); - this.remoteIdentityFingerprint = (TextView)findViewById(R.id.friend_reads); + scanFragment.setScanListener(this); + displayFragment.setClickListener(this); + + initFragment(android.R.id.content, displayFragment, masterSecret, dynamicLanguage.getCurrentLocale(), extras); } @Override - public void onResume() { - super.onResume(); + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: finish(); return true; + } - this.recipient = RecipientFactory.getRecipientForId(this, this.getIntent().getLongExtra("recipient", -1), true); + return false; + } - initializeFingerprints(); + @Override + public void onQrDataFound(final String data) { + Util.runOnMain(new Runnable() { + @Override + public void run() { + ((Vibrator)getSystemService(Context.VIBRATOR_SERVICE)).vibrate(50); + + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + transaction.setCustomAnimations(R.anim.slide_from_bottom, R.anim.slide_to_top); + + displayFragment.setScannedFingerprint(data); + transaction.replace(android.R.id.content, displayFragment) + .commit(); + } + }); + } + + @Override + public void onClick(View v) { + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + transaction.setCustomAnimations(R.anim.slide_from_top, R.anim.slide_to_bottom, + R.anim.slide_from_bottom, R.anim.slide_to_top); + + transaction.replace(android.R.id.content, scanFragment) + .addToBackStack(null) + .commit(); } - private void initializeFingerprints() { - if (!IdentityKeyUtil.hasIdentityKey(this)) { - localIdentityFingerprint.setText(R.string.VerifyIdentityActivity_you_do_not_have_an_identity_key); - return; + public static class VerifyDisplayFragment extends Fragment { + + public static final String REMOTE_NUMBER = "remote_number"; + public static final String REMOTE_IDENTITY = "remote_identity"; + public static final String LOCAL_IDENTITY = "local_identity"; + public static final String LOCAL_NUMBER = "local_number"; + + private String localNumber; + private String remoteNumber; + + private IdentityKey localIdentity; + private IdentityKey remoteIdentity; + + private Fingerprint fingerprint; + + private View container; + private ImageView qrCode; + private ImageView qrVerified; + private View.OnClickListener clickListener; + + private TextView[] codes = new TextView[12]; + private boolean animateSuccessOnDraw = false; + private boolean animateFailureOnDraw = false; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) { + this.container = ViewUtil.inflate(inflater, viewGroup, R.layout.verify_display_fragment); + this.qrCode = ViewUtil.findById(container, R.id.qr_code); + this.qrVerified = ViewUtil.findById(container, R.id.qr_verified); + this.codes[0] = ViewUtil.findById(container, R.id.code_first); + this.codes[1] = ViewUtil.findById(container, R.id.code_second); + this.codes[2] = ViewUtil.findById(container, R.id.code_third); + this.codes[3] = ViewUtil.findById(container, R.id.code_fourth); + this.codes[4] = ViewUtil.findById(container, R.id.code_fifth); + this.codes[5] = ViewUtil.findById(container, R.id.code_sixth); + this.codes[6] = ViewUtil.findById(container, R.id.code_seventh); + this.codes[7] = ViewUtil.findById(container, R.id.code_eighth); + this.codes[8] = ViewUtil.findById(container, R.id.code_ninth); + this.codes[9] = ViewUtil.findById(container, R.id.code_tenth); + this.codes[10] = ViewUtil.findById(container, R.id.code_eleventh); + this.codes[11] = ViewUtil.findById(container, R.id.code_twelth); + + this.qrCode.setOnClickListener(clickListener); + + return container; } - localIdentityFingerprint.setText(Hex.toString(IdentityKeyUtil.getIdentityKey(this).serialize())); + @Override + public void onCreate(Bundle bundle) { + super.onCreate(bundle); - IdentityKey identityKey = getRemoteIdentityKey(masterSecret, recipient); + this.localNumber = getArguments().getString(LOCAL_NUMBER); + this.localIdentity = ((IdentityKeyParcelable)getArguments().getParcelable(LOCAL_IDENTITY)).get(); + this.remoteNumber = getArguments().getString(REMOTE_NUMBER); + this.remoteIdentity = ((IdentityKeyParcelable)getArguments().getParcelable(REMOTE_IDENTITY)).get(); + this.fingerprint = new NumericFingerprintGenerator(5200).createFor(localNumber, localIdentity, + remoteNumber, remoteIdentity); + } + + @Override + public void onResume() { + super.onResume(); - if (identityKey == null) { - remoteIdentityFingerprint.setText(R.string.VerifyIdentityActivity_recipient_has_no_identity_key); - } else { - remoteIdentityFingerprint.setText(Hex.toString(identityKey.serialize())); + setFingerprintViews(fingerprint); + + if (animateSuccessOnDraw) { + animateSuccessOnDraw = false; + animateVerifiedSuccess(); + } else if (animateFailureOnDraw) { + animateFailureOnDraw = false; + animateVerifiedFailure();; + } } - } - @Override - protected void initiateDisplay() { - if (!IdentityKeyUtil.hasIdentityKey(this)) { - Toast.makeText(this, - R.string.VerifyIdentityActivity_you_don_t_have_an_identity_key_exclamation, - Toast.LENGTH_LONG).show(); - return; + public void setScannedFingerprint(String scanned) { + try { + if (fingerprint.getScannableFingerprint().compareTo(scanned.getBytes("ISO-8859-1"))) { + this.animateSuccessOnDraw = true; + } else { + this.animateFailureOnDraw = true; + } + } catch (FingerprintVersionMismatchException e) { + Log.w(TAG, e); + Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_your_contact_is_running_an_old_version_of_signal, Toast.LENGTH_LONG).show(); + } catch (FingerprintIdentifierMismatchException e) { + Log.w(TAG, e); + Toast.makeText(getActivity(), getActivity().getString(R.string.VerifyIdentityActivity_you_re_attempting_to_verify_security_numbers_with, e.getRemoteIdentifier(), e.getScannedRemoteIdentifier()), Toast.LENGTH_LONG).show(); + } catch (FingerprintParsingException e) { + Log.w(TAG, e); + Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_the_scanned_qr_code_is_not_a_correctly_formatted_security_number, Toast.LENGTH_LONG).show(); + } catch (UnsupportedEncodingException e) { + throw new AssertionError(e); + } } - super.initiateDisplay(); - } + public void setClickListener(View.OnClickListener listener) { + this.clickListener = listener; + } - @Override - protected void initiateScan() { - IdentityKey identityKey = getRemoteIdentityKey(masterSecret, recipient); + private void setFingerprintViews(Fingerprint fingerprint) { + String digits = fingerprint.getDisplayableFingerprint().getDisplayText(); + int partSize = digits.length() / codes.length; + + for (int i=0;i. - */ -package org.thoughtcrime.securesms; - -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.widget.TextView; - -import org.thoughtcrime.securesms.crypto.MasterSecret; -import org.thoughtcrime.securesms.util.Hex; -import org.whispersystems.libsignal.IdentityKey; -import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable; - -/** - * Activity for displaying an identity key. - * - * @author Moxie Marlinspike - */ -public class ViewIdentityActivity extends KeyScanningActivity { - - public static final String IDENTITY_KEY = "identity_key"; - public static final String TITLE = "title"; - - private TextView identityFingerprint; - private IdentityKey identityKey; - - @Override - protected void onCreate(Bundle state, @NonNull MasterSecret masterSecret) { - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - setContentView(R.layout.view_identity_activity); - - initialize(); - } - - protected void initialize() { - initializeResources(); - initializeFingerprint(); - } - - private void initializeFingerprint() { - if (identityKey == null) { - identityFingerprint.setText(R.string.ViewIdentityActivity_you_do_not_have_an_identity_key); - } else { - identityFingerprint.setText(Hex.toString(identityKey.serialize())); - } - } - - private void initializeResources() { - IdentityKeyParcelable identityKeyParcelable = getIntent().getParcelableExtra(IDENTITY_KEY); - - if (identityKeyParcelable == null) { - throw new AssertionError("No identity key!"); - } - - this.identityKey = identityKeyParcelable.get(); - this.identityFingerprint = (TextView)findViewById(R.id.identity_fingerprint); - String title = getIntent().getStringExtra(TITLE); - - if (title != null) { - getSupportActionBar().setTitle(getIntent().getStringExtra(TITLE)); - } - } - - @Override - protected String getScanString() { - return getString(R.string.ViewIdentityActivity_scan_contacts_qr_code); - } - - @Override - protected String getDisplayString() { - return getString(R.string.ViewIdentityActivity_display_your_qr_code); - } - - @Override - protected IdentityKey getIdentityKeyToCompare() { - return identityKey; - } - - @Override - protected IdentityKey getIdentityKeyToDisplay() { - return identityKey; - } - - @Override - protected String getNotVerifiedMessage() { - return getString(R.string.ViewIdentityActivity_warning_the_scanned_key_does_not_match_exclamation); - } - - @Override - protected String getNotVerifiedTitle() { - return getString(R.string.ViewIdentityActivity_not_verified_exclamation); - } - - @Override - protected String getVerifiedMessage() { - return getString(R.string.ViewIdentityActivity_the_scanned_key_matches_exclamation); - } - - @Override - protected String getVerifiedTitle() { - return getString(R.string.ViewIdentityActivity_verified_exclamation); - } -} diff --git a/src/org/thoughtcrime/securesms/ViewLocalIdentityActivity.java b/src/org/thoughtcrime/securesms/ViewLocalIdentityActivity.java deleted file mode 100644 index 676f56124d..0000000000 --- a/src/org/thoughtcrime/securesms/ViewLocalIdentityActivity.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2011 Whisper Systems - * Copyright (C) 2013 Open Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.thoughtcrime.securesms; - -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; - -import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; -import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable; -import org.thoughtcrime.securesms.crypto.MasterSecret; - -/** - * Activity that displays the local identity key and offers the option to regenerate it. - * - * @author Moxie Marlinspike - */ -public class ViewLocalIdentityActivity extends ViewIdentityActivity { - - @Override - protected void onCreate(Bundle icicle, @NonNull MasterSecret masterSecret) { - getIntent().putExtra(ViewIdentityActivity.IDENTITY_KEY, - new IdentityKeyParcelable(IdentityKeyUtil.getIdentityKey(this))); - getIntent().putExtra(ViewIdentityActivity.TITLE, - getString(R.string.ViewIdentityActivity_your_identity_fingerprint)); - super.onCreate(icicle, masterSecret); - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - super.onPrepareOptionsMenu(menu); - - MenuInflater inflater = this.getMenuInflater(); - inflater.inflate(R.menu.local_identity, menu); - - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - super.onOptionsItemSelected(item); - - switch (item.getItemId()) { - case android.R.id.home:finish(); return true; - } - - return false; - } -} diff --git a/src/org/thoughtcrime/securesms/components/SquareImageView.java b/src/org/thoughtcrime/securesms/components/SquareImageView.java new file mode 100644 index 0000000000..57ba997203 --- /dev/null +++ b/src/org/thoughtcrime/securesms/components/SquareImageView.java @@ -0,0 +1,31 @@ +package org.thoughtcrime.securesms.components; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.util.AttributeSet; +import android.widget.ImageView; + +public class SquareImageView extends ImageView { + public SquareImageView(Context context) { + super(context); + } + + public SquareImageView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public SquareImageView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public SquareImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, widthMeasureSpec); + } +} diff --git a/src/org/thoughtcrime/securesms/qr/QrCode.java b/src/org/thoughtcrime/securesms/qr/QrCode.java new file mode 100644 index 0000000000..e6d2b5dc11 --- /dev/null +++ b/src/org/thoughtcrime/securesms/qr/QrCode.java @@ -0,0 +1,37 @@ +package org.thoughtcrime.securesms.qr; + +import android.graphics.Bitmap; +import android.graphics.Color; +import android.support.annotation.NonNull; +import android.util.Log; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.QRCodeWriter; + +public class QrCode { + + public static final String TAG = QrCode.class.getSimpleName(); + + public static @NonNull Bitmap create(String data) { + try { + BitMatrix result = new QRCodeWriter().encode(data, BarcodeFormat.QR_CODE, 512, 512); + Bitmap bitmap = Bitmap.createBitmap(result.getWidth(), result.getHeight(), Bitmap.Config.ARGB_8888); + + for (int y = 0; y < result.getHeight(); y++) { + for (int x = 0; x < result.getWidth(); x++) { + if (result.get(x, y)) { + bitmap.setPixel(x, y, Color.BLACK); + } + } + } + + return bitmap; + } catch (WriterException e) { + Log.w(TAG, e); + return Bitmap.createBitmap(512, 512, Bitmap.Config.ARGB_8888); + } + } + +} diff --git a/src/org/thoughtcrime/securesms/qr/ScanListener.java b/src/org/thoughtcrime/securesms/qr/ScanListener.java new file mode 100644 index 0000000000..83faae9907 --- /dev/null +++ b/src/org/thoughtcrime/securesms/qr/ScanListener.java @@ -0,0 +1,5 @@ +package org.thoughtcrime.securesms.qr; + +public interface ScanListener { + public void onQrDataFound(String data); +} diff --git a/src/org/thoughtcrime/securesms/qr/ScanningThread.java b/src/org/thoughtcrime/securesms/qr/ScanningThread.java new file mode 100644 index 0000000000..f37374e296 --- /dev/null +++ b/src/org/thoughtcrime/securesms/qr/ScanningThread.java @@ -0,0 +1,125 @@ +package org.thoughtcrime.securesms.qr; + +import android.content.res.Configuration; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; + +import com.google.zxing.BinaryBitmap; +import com.google.zxing.ChecksumException; +import com.google.zxing.DecodeHintType; +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.PreviewFrame; +import org.thoughtcrime.securesms.util.Util; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +public class ScanningThread extends Thread implements CameraView.PreviewCallback { + + private static final String TAG = ScanningThread.class.getSimpleName(); + + private final QRCodeReader reader = new QRCodeReader(); + private final AtomicReference scanListener = new AtomicReference<>(); + private final Map hints = new HashMap<>(); + + private boolean scanning = true; + private PreviewFrame previewFrame; + + public void setCharacterSet(String characterSet) { + hints.put(DecodeHintType.CHARACTER_SET, characterSet); + } + + public void setScanListener(ScanListener scanListener) { + this.scanListener.set(scanListener); + } + + @Override + public void onPreviewFrame(@NonNull PreviewFrame previewFrame) { + try { + synchronized (this) { + this.previewFrame = previewFrame; + this.notify(); + } + } catch (RuntimeException e) { + Log.w(TAG, e); + } + } + + + @Override + public void run() { + while (true) { + PreviewFrame ourFrame; + + synchronized (this) { + while (scanning && previewFrame == null) { + Util.wait(this, 0); + } + + if (!scanning) return; + else ourFrame = previewFrame; + + previewFrame = null; + } + + String data = getScannedData(ourFrame.getData(), ourFrame.getWidth(), ourFrame.getHeight(), ourFrame.getOrientation()); + ScanListener scanListener = this.scanListener.get(); + + if (data != null && scanListener != null) { + scanListener.onQrDataFound(data); + return; + } + } + } + + public void stopScanning() { + synchronized (this) { + scanning = false; + notify(); + } + } + + private @Nullable String getScannedData(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, hints); + + if (result != null) return result.getText(); + + } catch (NullPointerException | ChecksumException | FormatException e) { + Log.w(TAG, e); + } catch (NotFoundException e) { + // Thanks ZXing... + } + + return null; + } +}