From 7c0bf0c871c96dd89605e3daf07d3c332522fdef Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Thu, 19 Nov 2015 10:21:19 -0800 Subject: [PATCH] Device provisioning Closes #4553 // FREEBIE --- AndroidManifest.xml | 8 +- build.gradle | 4 + .../ic_add_white_original_24dp.png | Bin 0 -> 223 bytes res/drawable-hdpi/ic_devices_white.png | Bin 0 -> 7677 bytes res/drawable-hdpi/ic_laptop_black_32dp.png | Bin 0 -> 525 bytes res/drawable-hdpi/ic_laptop_light_32dp.png | Bin 0 -> 774 bytes .../ic_add_white_original_24dp.png | Bin 0 -> 174 bytes res/drawable-mdpi/ic_devices_white.png | Bin 0 -> 4898 bytes res/drawable-mdpi/ic_laptop_black_32dp.png | Bin 0 -> 430 bytes res/drawable-mdpi/ic_laptop_light_32dp.png | Bin 0 -> 625 bytes .../ic_add_white_original_24dp.png | Bin 0 -> 198 bytes res/drawable-xhdpi/ic_devices_white.png | Bin 0 -> 10745 bytes res/drawable-xhdpi/ic_laptop_black_32dp.png | Bin 0 -> 727 bytes res/drawable-xhdpi/ic_laptop_light_32dp.png | Bin 0 -> 1112 bytes .../ic_add_white_original_24dp.png | Bin 0 -> 222 bytes res/drawable-xxhdpi/ic_devices_white.png | Bin 0 -> 17164 bytes res/drawable-xxhdpi/ic_laptop_black_32dp.png | Bin 0 -> 397 bytes res/drawable-xxhdpi/ic_laptop_light_32dp.png | Bin 0 -> 379 bytes .../ic_add_white_original_24dp.png | Bin 0 -> 269 bytes res/drawable-xxxhdpi/ic_devices_white.png | Bin 0 -> 9165 bytes res/layout/device_add_fragment.xml | 52 +++++ res/layout/device_link_fragment.xml | 87 +++++++ res/layout/device_list_fragment.xml | 29 ++- res/transition/fragment_shared.xml | 5 + res/values/attrs.xml | 9 + res/values/strings.xml | 9 +- res/values/themes.xml | 4 +- res/xml/preferences.xml | 8 +- .../ApplicationPreferencesActivity.java | 6 +- .../securesms/DeviceActivity.java | 194 +++++++++++++++ .../securesms/DeviceAddFragment.java | 220 ++++++++++++++++++ .../securesms/DeviceLinkFragment.java | 56 +++++ .../securesms/DeviceListActivity.java | 215 ----------------- .../securesms/DeviceListFragment.java | 192 +++++++++++++++ .../securesms/components/ShapeScrim.java | 107 +++++++++ .../components/camera/CameraUtils.java | 2 + .../components/camera/CameraView.java | 82 ++++++- .../TextSecureCommunicationModule.java | 4 +- 38 files changed, 1047 insertions(+), 246 deletions(-) create mode 100644 res/drawable-hdpi/ic_add_white_original_24dp.png create mode 100644 res/drawable-hdpi/ic_devices_white.png create mode 100644 res/drawable-hdpi/ic_laptop_black_32dp.png create mode 100644 res/drawable-hdpi/ic_laptop_light_32dp.png create mode 100644 res/drawable-mdpi/ic_add_white_original_24dp.png create mode 100644 res/drawable-mdpi/ic_devices_white.png create mode 100644 res/drawable-mdpi/ic_laptop_black_32dp.png create mode 100644 res/drawable-mdpi/ic_laptop_light_32dp.png create mode 100644 res/drawable-xhdpi/ic_add_white_original_24dp.png create mode 100644 res/drawable-xhdpi/ic_devices_white.png create mode 100644 res/drawable-xhdpi/ic_laptop_black_32dp.png create mode 100644 res/drawable-xhdpi/ic_laptop_light_32dp.png create mode 100644 res/drawable-xxhdpi/ic_add_white_original_24dp.png create mode 100644 res/drawable-xxhdpi/ic_devices_white.png create mode 100644 res/drawable-xxhdpi/ic_laptop_black_32dp.png create mode 100644 res/drawable-xxhdpi/ic_laptop_light_32dp.png create mode 100644 res/drawable-xxxhdpi/ic_add_white_original_24dp.png create mode 100644 res/drawable-xxxhdpi/ic_devices_white.png create mode 100644 res/layout/device_add_fragment.xml create mode 100644 res/layout/device_link_fragment.xml create mode 100644 res/transition/fragment_shared.xml create mode 100644 src/org/thoughtcrime/securesms/DeviceActivity.java create mode 100644 src/org/thoughtcrime/securesms/DeviceAddFragment.java create mode 100644 src/org/thoughtcrime/securesms/DeviceLinkFragment.java delete mode 100644 src/org/thoughtcrime/securesms/DeviceListActivity.java create mode 100644 src/org/thoughtcrime/securesms/DeviceListFragment.java create mode 100644 src/org/thoughtcrime/securesms/components/ShapeScrim.java 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 0000000000000000000000000000000000000000..481643ecd5e5c361bdf5440f80b6ead58c015a87 GIT binary patch literal 223 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8K;Lb6AYF9SoB8UsT^3j@P1pisjL z28L1t28LG&3=CE?7#PG0=Ijcz0ZK3>dAqwX{BQ3+vmeOgEbxddW?|m)`b<0N2FLh>sBaz zbNHm-*5u;7=mneO8+En?J13^G1)j}b{4(Q;!Yv7xt%vHjER#LP#>jBmH-z#3;kA!} PRxo(F`njxgN@xNAMifE{ literal 0 HcmV?d00001 diff --git a/res/drawable-hdpi/ic_devices_white.png b/res/drawable-hdpi/ic_devices_white.png new file mode 100644 index 0000000000000000000000000000000000000000..38d1c0c046738954235da776ab25b6ec2be6c964 GIT binary patch literal 7677 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 a;PDaPOj1z48yS)!FL`NYsR{{`fd2*EI<7zf literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c87e01d547c90f0299a4f5f105514bd432d942bf GIT binary patch literal 525 zcmV+o0`mQdP))$or%fVh+GScZ4V>u?Q&7t%Eu1ub=<+Eyz?}?` zcgPLv|6^e9U5%jeN7nd{$Svf%X~1J(3AqAs;J$)-rN&h&b%4ZQA=h-Bv?Vli17^tx zCgPt!EujQvkX-#atEcvX0OHrme8wcdLVZYnI#a=$w-YyMGYcH$?j9vt18+bY-hec` z0cm&x((neP(P;xxx3DZkj&jOC1j(~Em1ouIDulVv*msl!_>GJj_N>NKUkBMMrG5VI zv3knDH>hmHP}f7`HOOD;%{7;_edRSKG%>9*1xH#X3p>bLt8)`bT5gcrS%O2&k^W1m zi$ml&sGKB!P+hZbIMG@TkgH5CT&=az)OpKt-^}qquy~x&{!(!=gb^ zfJ*NNxs&F~PM3awG|0e zDwWFX>+9=xs;U-j+s5l$0-*CGNfK78)u`L;zOUEocguez=mDT9MFGy>R&Bf$x7a11+3D3eSkqtc%;olezuyM1pM#+RF$n^!b! zUhgwL;Hx2zQ5e>0wKvsj_0woH(o|Jdd}o!lTCMLv8Z5MR4YtVx znd;q0*yXQ+b(ZTg`QP*J{u+HMqyc!72H?%l00030|1ZVWM)KV-?*IS*07*qoM6N<$ Ef>Bs(f&c&j literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..977dd3427ade9fa5002997d3e3df916aa8861983 GIT binary patch literal 174 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 Y|3TS{XMid3`v3p{07*qoM6N<$g4Hs)v;Y7A literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..58d056ffd68c2c6f71e819587b82a1200ab6b7e5 GIT binary patch literal 625 zcmV-%0*?KOP)Z;7K68rn8EqNMU6c@h_yX6@qn&)RNzj)CN5JDbhkjYgx-%jNQDfJA6PtyX*7?RKZG>t5Nm z&63^;%6u4xtl4b7oX_XA@p$}&w8=mQ`XS~QB)(d$y#0RfAq&0^z))Le5z%KI$8n3r z;uUG()ir5zie5Q{!SGNh6r5hK*O^QvACLu`-kYdvjz*)=>i7HaNkemzHf4aR{g&%# z-fT9v>-GA(VdTGnIMsGXM@yayrc!i#mS%)ahd_Oj>a#f+nu9pSoqD7huLJi_zKG2x z5HapMg9(ruAvKU1NDcf}2UM}3`eD;B70?{2=ZJVMhV0I_5p(CAWq$R znJP@W7f5G-1Ggj|bUK|ktyb&Ry&s$m%~39wpKQ0=AeYNg(2n|7hQnb%3$xkmCk%4T z>-G9``a4PZzRzHQbfgwu3DUOn`FsKQfCsn(d_xAdl}hEA=Xus{w>w}6HJgqQ2B;zZ z7!HST(ds|q`5|;&YdW1Sl|u`{H%L_(Mw~>$S3t!3uK;u(Aq&R_wOA~4r;-DiIEQuw z+=}G=Lmw?Z7!0msYZAGL{sI5am7KtnC^r8!F5U|+Zs2DC00960gspX&jxMyC00000 LNkvXXu0mjfUn?Qr literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..67042105d29bc5676206dbdb1cdc9359e58bd63d GIT binary patch literal 198 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}tg=CK)Uj~LMH3o);76yi2K%s^g z3=E|}g|8AA7_4S6Fo+k-*%fF5lweBoc6VX;-`;;_Kaj^+;1OBOz`!jG!i)^F=12eq z?L1u^Ln02py|$5)K|z4oVe|hBQ9Bz}EZMQ1`FhKTnUCMHGB9jmw3Y!X{lPFtFM;7Y kgDFtxY{Pe1h8HYi&yV;pzTM$H6)4T%>FVdQ&MBb@0Q4<48~^|S literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi/ic_devices_white.png b/res/drawable-xhdpi/ic_devices_white.png new file mode 100644 index 0000000000000000000000000000000000000000..b77159e11902ddbe9df8cc51f38f3be2b0002529 GIT binary patch literal 10745 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 Hl+XkK%!*PS literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..34e1a1efb479dabcadb3c57db37c05c9a18baf60 GIT binary patch literal 1112 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 NURp(}Ov2>j{{_?fW`zI% literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c86e7c4adaebb2effb5b2fe594f24b52a80c471f GIT binary patch literal 397 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD0wg^q?%xcgn3BBRT^Rni_n+Ah4nJ za0`PlBg3pY5H=O_WRrdjOsGSi=UljU|^Ki>{?sjm!~-Vq%jrXKXy6T{-Wjr|SFsTT_-y?Er&6yP`Cv z8*eB&d$ltEhmW!FV*6{M!dD+1kC&GIzc1|6BL?HikF70xAFr!TFN?kM?YJ*@L*qkf z?^XN$2L@Sagda9|xFC(=fzSOrbL-DeX^!`-v-@;9^HTl)y{te-;{grt#p?WWULV}f zJ->1BOWyN`8|G#nFst!!4?n#A_Le_|-mC{aMZfmE`|mM(#^J1}nRos#UA6I&PjYNe ztj*ycAK#mrT#b7^zrXHUobc88y)z~14|eVs0@>%_0s>!V<|)Ll)V>gI@((2F>FVdQ I&MBb@0Qke4m;e9( literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f13d25e424ed10ce5f462aa2034c31faa1d99e5f GIT binary patch literal 379 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD0wg^q?%xcgn3BBRT^Rni_n+Ah4nJ za0`PlBg3pY5H=O_WRrdj9SV^c5mIuz`)4k>EaktaqI0ZU%n;-fwqV1d)a3> zXR2I$oUy3U`E2J3?%t35ubfg1-tQI>tDUh(O+gR@E~bCJxkkfwe#WPqz1ySMqUzJN z&tKcUhHZjM{A-1kd+e+npHDR@t4U{y)Rtw^{U>&D#nw8J2S$~gJN|GVy!2S@)c5Bz z_Q>9fm^@u2fAx2V8?Wo+>!fHR>}wiu?SDZvpq;P43_ST>RVjzgc|VCaI+jw^wMa{P^d;{@++Z>s+ST|9f-U o^SWQ$|NLhv*kC0PShT)`Y0GbWhPF3*W`aaLUHx3vIVCg!0M-kRwEzGB literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..2bef0595839a40b3d454a20cd48288a45da81a8b GIT binary patch literal 269 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD0wg^q?%xcg6p}rHd>I(3)EF2VS{N990fib~ zFff!FFfhDIU|_JC!N4G1FlSew4N!t9$=lt9;eUJonf*W>XMsm#F#`j)FbFd;%$g$s z6m0c$aSW-r_4c+S*8v3{7KiX(|MhS0UcN1%)8co-fdv98hKt|My$4i?1nizhg;mz& z9Opz512aXu~v2$8Rl{>+G`sNq!S{wXM8?M!*oXR_}uy`q%@68CiV Kb6Mw<&;$TV=|M&S literal 0 HcmV?d00001 diff --git a/res/drawable-xxxhdpi/ic_devices_white.png b/res/drawable-xxxhdpi/ic_devices_white.png new file mode 100644 index 0000000000000000000000000000000000000000..e3f2216f4dfef42d130fab206e6dadba7c462be9 GIT binary patch literal 9165 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 + + + + + + + + + + + + + + + + + + + + + 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 {