Support for updated fingerprint format
// FREEBIE
|
@ -262,13 +262,6 @@
|
||||||
android:windowSoftInputMode="stateHidden"
|
android:windowSoftInputMode="stateHidden"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".ViewIdentityActivity"
|
|
||||||
android:label="@string/AndroidManifest__public_identity_key"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".ViewLocalIdentityActivity"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".PassphraseChangeActivity"
|
<activity android:name=".PassphraseChangeActivity"
|
||||||
android:label="@string/AndroidManifest__change_passphrase"
|
android:label="@string/AndroidManifest__change_passphrase"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
BIN
res/drawable-hdpi/ic_check_white_48dp.png
Normal file
After Width: | Height: | Size: 276 B |
BIN
res/drawable-hdpi/ic_close_white_48dp.png
Normal file
After Width: | Height: | Size: 347 B |
BIN
res/drawable-mdpi/ic_check_white_48dp.png
Normal file
After Width: | Height: | Size: 199 B |
BIN
res/drawable-mdpi/ic_close_white_48dp.png
Normal file
After Width: | Height: | Size: 257 B |
BIN
res/drawable-xhdpi/ic_check_white_48dp.png
Normal file
After Width: | Height: | Size: 308 B |
BIN
res/drawable-xhdpi/ic_close_white_48dp.png
Normal file
After Width: | Height: | Size: 436 B |
BIN
res/drawable-xxhdpi/ic_check_white_48dp.png
Normal file
After Width: | Height: | Size: 386 B |
BIN
res/drawable-xxhdpi/ic_close_white_48dp.png
Normal file
After Width: | Height: | Size: 524 B |
BIN
res/drawable-xxxhdpi/ic_check_white_48dp.png
Normal file
After Width: | Height: | Size: 466 B |
BIN
res/drawable-xxxhdpi/ic_close_white_48dp.png
Normal file
After Width: | Height: | Size: 707 B |
5
res/drawable/qr_code_background.xml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
<solid android:color="@color/white"/>
|
||||||
|
</shape>
|
151
res/layout/verify_display_fragment.xml
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:fillViewport="true">
|
||||||
|
|
||||||
|
<LinearLayout android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="20dp"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:background="?verification_background"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<FrameLayout android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.components.SquareImageView
|
||||||
|
android:id="@+id/qr_code"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="20dp"
|
||||||
|
android:padding="20dp"
|
||||||
|
android:background="@drawable/qr_code_background"
|
||||||
|
tools:src="@drawable/splash_logo"/>
|
||||||
|
|
||||||
|
<TextView android:id="@+id/tap_label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom|center_horizontal"
|
||||||
|
android:layout_marginBottom="35dp"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:text="@string/verify_display_fragment__tap_to_scan"/>
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.components.SquareImageView
|
||||||
|
android:id="@+id/qr_verified"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="20dp"
|
||||||
|
android:padding="20dp"
|
||||||
|
android:src="@drawable/ic_check_white_48dp"
|
||||||
|
android:background="@drawable/qr_code_background"
|
||||||
|
android:backgroundTint="@color/green_500"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<TableLayout android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp">
|
||||||
|
|
||||||
|
<TableRow android:gravity="center_horizontal">
|
||||||
|
<TextView android:id="@+id/code_first"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/IdentityKey"
|
||||||
|
tools:text="22934"/>
|
||||||
|
|
||||||
|
<TextView android:id="@+id/code_second"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="20dp"
|
||||||
|
style="@style/IdentityKey"
|
||||||
|
tools:text="56944"/>
|
||||||
|
|
||||||
|
<TextView android:id="@+id/code_third"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="20dp"
|
||||||
|
style="@style/IdentityKey"
|
||||||
|
tools:text="42738"/>
|
||||||
|
|
||||||
|
<TextView android:id="@+id/code_fourth"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="20dp"
|
||||||
|
style="@style/IdentityKey"
|
||||||
|
tools:text="20038"/>
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow android:gravity="center_horizontal">
|
||||||
|
<TextView android:id="@+id/code_fifth"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/IdentityKey"
|
||||||
|
tools:text="34431"/>
|
||||||
|
|
||||||
|
<TextView android:id="@+id/code_sixth"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="20dp"
|
||||||
|
style="@style/IdentityKey"
|
||||||
|
tools:text="24922"/>
|
||||||
|
|
||||||
|
<TextView android:id="@+id/code_seventh"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="20dp"
|
||||||
|
style="@style/IdentityKey"
|
||||||
|
tools:text="58594"/>
|
||||||
|
|
||||||
|
<TextView android:id="@+id/code_eighth"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="20dp"
|
||||||
|
style="@style/IdentityKey"
|
||||||
|
tools:text="24109"/>
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
|
<TableRow android:gravity="center_horizontal">
|
||||||
|
<TextView android:id="@+id/code_ninth"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/IdentityKey"
|
||||||
|
tools:text="00257"/>
|
||||||
|
|
||||||
|
<TextView android:id="@+id/code_tenth"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="20dp"
|
||||||
|
style="@style/IdentityKey"
|
||||||
|
tools:text="34956"/>
|
||||||
|
|
||||||
|
<TextView android:id="@+id/code_eleventh"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="20dp"
|
||||||
|
style="@style/IdentityKey"
|
||||||
|
tools:text="32440"/>
|
||||||
|
|
||||||
|
<TextView android:id="@+id/code_twelth"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="20dp"
|
||||||
|
style="@style/IdentityKey"
|
||||||
|
tools:text="15774"/>
|
||||||
|
</TableRow>
|
||||||
|
</TableLayout>
|
||||||
|
|
||||||
|
<TextView android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="25dp"
|
||||||
|
android:textSize="17sp"
|
||||||
|
android:lineSpacingExtra="3sp"
|
||||||
|
android:text="@string/verify_display_fragment__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"/>
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
|
@ -1,49 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="fill_parent"
|
|
||||||
android:fillViewport="true">
|
|
||||||
|
|
||||||
<LinearLayout android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:padding="20dp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
|
||||||
android:text="@string/verify_identity_activity__their_identity_they_read"
|
|
||||||
android:layout_marginBottom="8dp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
|
||||||
android:typeface="monospace"
|
|
||||||
android:fontFamily="monospace"
|
|
||||||
android:id="@+id/friend_reads"
|
|
||||||
android:text=""
|
|
||||||
android:layout_marginBottom="8dp" />
|
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/verify_identity_activity__your_identity_you_read"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
|
||||||
android:layout_marginBottom="8dp" />
|
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:id="@+id/you_read"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
|
||||||
android:typeface="monospace"
|
|
||||||
android:fontFamily="monospace"
|
|
||||||
android:text="" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
</ScrollView>
|
|
27
res/layout/verify_scan_fragment.xml
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.components.camera.CameraView
|
||||||
|
android:id="@+id/scanner"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:camera="0"/>
|
||||||
|
|
||||||
|
<LinearLayout android:id="@+id/overlay"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.components.ShapeScrim
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:radius="0.3"
|
||||||
|
app:shape="circle"/>
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
|
@ -1,23 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="fill_parent">
|
|
||||||
|
|
||||||
<LinearLayout android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginLeft="16dip"
|
|
||||||
android:layout_marginRight="16dip"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
|
||||||
android:typeface="monospace"
|
|
||||||
android:id="@+id/identity_fingerprint"
|
|
||||||
android:text=""
|
|
||||||
android:padding="7dip" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
</ScrollView>
|
|
|
@ -129,6 +129,8 @@
|
||||||
|
|
||||||
<attr name="recipient_preference_blocked" format="color"/>
|
<attr name="recipient_preference_blocked" format="color"/>
|
||||||
|
|
||||||
|
<attr name="verification_background" format="color"/>
|
||||||
|
|
||||||
<declare-styleable name="ColorPreference">
|
<declare-styleable name="ColorPreference">
|
||||||
<attr name="itemLayout" format="reference" />
|
<attr name="itemLayout" format="reference" />
|
||||||
<attr name="choices" format="reference" />
|
<attr name="choices" format="reference" />
|
||||||
|
|
|
@ -554,16 +554,10 @@
|
||||||
<string name="ThreadRecord_disappearing_message_time_updated_to_s">Disappearing message time set to %s</string>
|
<string name="ThreadRecord_disappearing_message_time_updated_to_s">Disappearing message time set to %s</string>
|
||||||
|
|
||||||
<!-- VerifyIdentityActivity -->
|
<!-- VerifyIdentityActivity -->
|
||||||
<string name="VerifyIdentityActivity_you_do_not_have_an_identity_key">You do not have an identity key.</string>
|
<string name="VerifyIdentityActivity_your_contact_is_running_an_old_version_of_signal">Your contact is running an old version of Signal, please ask them to update before verifying security numbers.</string>
|
||||||
<string name="VerifyIdentityActivity_recipient_has_no_identity_key">Recipient has no identity key.</string>
|
<string name="VerifyIdentityActivity_you_re_attempting_to_verify_security_numbers_with">You\'re attempting to verify security numbers with %1$s, but scanned %2$s instead.</string>
|
||||||
<string name="VerifyIdentityActivity_recipient_has_no_identity_key_exclamation">Recipient has no identity key!</string>
|
<string name="VerifyIdentityActivity_the_scanned_qr_code_is_not_a_correctly_formatted_security_number">The scanned QR code is not a correctly formatted security number verification code. Please try scanning again.</string>
|
||||||
<string name="VerifyIdentityActivity_scan_contacts_qr_code">Scan contact\'s QR code</string>
|
|
||||||
<string name="VerifyIdentityActivity_display_your_qr_code">Display your QR code</string>
|
|
||||||
<string name="VerifyIdentityActivity_warning_the_scanned_key_does_not_match_please_check_the_fingerprint_text_carefully">WARNING, the scanned key DOES NOT match! Please check the fingerprint text carefully.</string>
|
|
||||||
<string name="VerifyIdentityActivity_not_verified_exclamation">NOT Verified!</string>
|
|
||||||
<string name="VerifyIdentityActivity_their_key_is_correct_it_is_also_necessary_to_verify_your_key_with_them_as_well">Their key is correct. It is also necessary to verify your key with them as well.</string>
|
|
||||||
<string name="VerifyIdentityActivity_verified_exclamation">Verified!</string>
|
|
||||||
<string name="VerifyIdentityActivity_you_don_t_have_an_identity_key_exclamation">You don\'t have an identity key!</string>
|
|
||||||
|
|
||||||
<!-- ViewIdentityActivity -->
|
<!-- ViewIdentityActivity -->
|
||||||
<string name="ViewIdentityActivity_you_do_not_have_an_identity_key">You do not have an identity key.</string>
|
<string name="ViewIdentityActivity_you_do_not_have_an_identity_key">You do not have an identity key.</string>
|
||||||
|
@ -935,10 +929,10 @@
|
||||||
<string name="recipients_panel__to"><small>Enter a name or number</small></string>
|
<string name="recipients_panel__to"><small>Enter a name or number</small></string>
|
||||||
<string name="recipients_panel__add_member">Add member</string>
|
<string name="recipients_panel__add_member">Add member</string>
|
||||||
|
|
||||||
<!-- verify_identity_activity -->
|
<!-- verify_display_fragment -->
|
||||||
<string name="verify_identity_activity__their_identity_they_read">Their identity (they read):</string>
|
<string name="verify_display_fragment__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">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.</string>
|
||||||
<string name="verify_identity_activity__your_identity_you_read">Your identity (you read):</string>
|
<string name="verify_display_fragment__tap_to_scan">Tap to scan</string>
|
||||||
|
|
||||||
<!-- message_details_header -->
|
<!-- message_details_header -->
|
||||||
<string name="message_details_header__issues_need_your_attention">Some issues need your attention.</string>
|
<string name="message_details_header__issues_need_your_attention">Some issues need your attention.</string>
|
||||||
<string name="message_details_header__sent">Sent</string>
|
<string name="message_details_header__sent">Sent</string>
|
||||||
|
|
|
@ -227,5 +227,10 @@
|
||||||
<item name="android:textOff">@null</item>
|
<item name="android:textOff">@null</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="IdentityKey">
|
||||||
|
<item name="android:fontFamily">monospace</item>
|
||||||
|
<item name="android:typeface">monospace</item>
|
||||||
|
<item name="android:textSize">17sp</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -127,6 +127,8 @@
|
||||||
<item name="conversation_number_picker_text_color_normal">@color/gray65</item>
|
<item name="conversation_number_picker_text_color_normal">@color/gray65</item>
|
||||||
<item name="conversation_number_picker_text_color_selected">@color/black</item>
|
<item name="conversation_number_picker_text_color_selected">@color/black</item>
|
||||||
|
|
||||||
|
<item name="verification_background">@color/gray5</item>
|
||||||
|
|
||||||
<item name="emoji_tab_strip_background">@color/gray12</item>
|
<item name="emoji_tab_strip_background">@color/gray12</item>
|
||||||
<item name="emoji_tab_indicator">#66555555</item>
|
<item name="emoji_tab_indicator">#66555555</item>
|
||||||
<item name="emoji_tab_underline">#44555555</item>
|
<item name="emoji_tab_underline">#44555555</item>
|
||||||
|
@ -235,6 +237,8 @@
|
||||||
<item name="conversation_item_received_text_secondary_color">#BFffffff</item>
|
<item name="conversation_item_received_text_secondary_color">#BFffffff</item>
|
||||||
<item name="conversation_item_sent_indicator_text_background">@drawable/conversation_item_sent_indicator_text_shape_dark</item>
|
<item name="conversation_item_sent_indicator_text_background">@drawable/conversation_item_sent_indicator_text_shape_dark</item>
|
||||||
|
|
||||||
|
<item name="verification_background">#ff333333</item>
|
||||||
|
|
||||||
<item name="dialog_info_icon">@drawable/ic_info_outline_dark</item>
|
<item name="dialog_info_icon">@drawable/ic_info_outline_dark</item>
|
||||||
<item name="dialog_alert_icon">@drawable/ic_warning_dark</item>
|
<item name="dialog_alert_icon">@drawable/ic_warning_dark</item>
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,8 @@
|
||||||
|
|
||||||
<Preference android:key="pref_key_recipient_identity"
|
<Preference android:key="pref_key_recipient_identity"
|
||||||
android:title="@string/recipient_preferences__verify_identity"
|
android:title="@string/recipient_preferences__verify_identity"
|
||||||
android:persistent="false"/>
|
android:persistent="false"
|
||||||
|
android:enabled="false"/>
|
||||||
|
|
||||||
<Preference android:key="pref_key_recipient_block"
|
<Preference android:key="pref_key_recipient_block"
|
||||||
android:title="@string/recipient_preferences__block"
|
android:title="@string/recipient_preferences__block"
|
||||||
|
|
|
@ -204,8 +204,8 @@ public class ConfirmIdentityDialog extends AlertDialog {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View widget) {
|
public void onClick(View widget) {
|
||||||
Intent intent = new Intent(context, VerifyIdentityActivity.class);
|
Intent intent = new Intent(context, VerifyIdentityActivity.class);
|
||||||
intent.putExtra("recipient", mismatch.getRecipientId());
|
intent.putExtra(VerifyIdentityActivity.RECIPIENT_ID, mismatch.getRecipientId());
|
||||||
intent.putExtra("remote_identity", new IdentityKeyParcelable(mismatch.getIdentityKey()));
|
intent.putExtra(VerifyIdentityActivity.RECIPIENT_IDENTITY, new IdentityKeyParcelable(mismatch.getIdentityKey()));
|
||||||
context.startActivity(intent);
|
context.startActivity(intent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import android.widget.Toast;
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||||
import org.thoughtcrime.securesms.push.TextSecureCommunicationFactory;
|
import org.thoughtcrime.securesms.push.TextSecureCommunicationFactory;
|
||||||
|
import org.thoughtcrime.securesms.qr.ScanListener;
|
||||||
import org.thoughtcrime.securesms.util.Base64;
|
import org.thoughtcrime.securesms.util.Base64;
|
||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
|
@ -34,7 +35,7 @@ import org.whispersystems.signalservice.internal.push.DeviceLimitExceededExcepti
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public class DeviceActivity extends PassphraseRequiredActionBarActivity
|
public class DeviceActivity extends PassphraseRequiredActionBarActivity
|
||||||
implements Button.OnClickListener, DeviceAddFragment.ScanListener, DeviceLinkFragment.LinkClickedListener
|
implements Button.OnClickListener, ScanListener, DeviceLinkFragment.LinkClickedListener
|
||||||
{
|
{
|
||||||
|
|
||||||
private static final String TAG = DeviceActivity.class.getSimpleName();
|
private static final String TAG = DeviceActivity.class.getSimpleName();
|
||||||
|
@ -95,11 +96,12 @@ public class DeviceActivity extends PassphraseRequiredActionBarActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUrlFound(final Uri uri) {
|
public void onQrDataFound(final String data) {
|
||||||
Util.runOnMain(new Runnable() {
|
Util.runOnMain(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
((Vibrator)getSystemService(Context.VIBRATOR_SERVICE)).vibrate(50);
|
((Vibrator)getSystemService(Context.VIBRATOR_SERVICE)).vibrate(50);
|
||||||
|
Uri uri = Uri.parse(data);
|
||||||
deviceLinkFragment.setLinkClickedListener(uri, DeviceActivity.this);
|
deviceLinkFragment.setLinkClickedListener(uri, DeviceActivity.this);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
|
|
@ -31,20 +31,17 @@ import com.google.zxing.qrcode.QRCodeReader;
|
||||||
import org.thoughtcrime.securesms.components.camera.CameraView;
|
import org.thoughtcrime.securesms.components.camera.CameraView;
|
||||||
import org.thoughtcrime.securesms.components.camera.CameraView.PreviewCallback;
|
import org.thoughtcrime.securesms.components.camera.CameraView.PreviewCallback;
|
||||||
import org.thoughtcrime.securesms.components.camera.CameraView.PreviewFrame;
|
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.Util;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
|
|
||||||
public class DeviceAddFragment extends Fragment implements PreviewCallback {
|
public class DeviceAddFragment extends Fragment {
|
||||||
|
|
||||||
private static final String TAG = DeviceAddFragment.class.getSimpleName();
|
|
||||||
|
|
||||||
private final QRCodeReader reader = new QRCodeReader();
|
|
||||||
|
|
||||||
private ViewGroup container;
|
private ViewGroup container;
|
||||||
private LinearLayout overlay;
|
private LinearLayout overlay;
|
||||||
private ImageView devicesImage;
|
private ImageView devicesImage;
|
||||||
private CameraView scannerView;
|
private CameraView scannerView;
|
||||||
private PreviewFrame previewFrame;
|
|
||||||
private ScanningThread scanningThread;
|
private ScanningThread scanningThread;
|
||||||
private ScanListener scanListener;
|
private ScanListener scanListener;
|
||||||
|
|
||||||
|
@ -54,8 +51,6 @@ public class DeviceAddFragment extends Fragment implements PreviewCallback {
|
||||||
this.overlay = ViewUtil.findById(this.container, R.id.overlay);
|
this.overlay = ViewUtil.findById(this.container, R.id.overlay);
|
||||||
this.scannerView = ViewUtil.findById(this.container, R.id.scanner);
|
this.scannerView = ViewUtil.findById(this.container, R.id.scanner);
|
||||||
this.devicesImage = ViewUtil.findById(this.container, R.id.devices);
|
this.devicesImage = ViewUtil.findById(this.container, R.id.devices);
|
||||||
this.scannerView.onResume();
|
|
||||||
this.scannerView.setPreviewCallback(this);
|
|
||||||
|
|
||||||
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
this.overlay.setOrientation(LinearLayout.HORIZONTAL);
|
this.overlay.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
@ -86,10 +81,10 @@ public class DeviceAddFragment extends Fragment implements PreviewCallback {
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
this.scannerView.onResume();
|
|
||||||
this.scannerView.setPreviewCallback(this);
|
|
||||||
this.previewFrame = null;
|
|
||||||
this.scanningThread = new ScanningThread();
|
this.scanningThread = new ScanningThread();
|
||||||
|
this.scanningThread.setScanListener(scanListener);
|
||||||
|
this.scannerView.onResume();
|
||||||
|
this.scannerView.setPreviewCallback(scanningThread);
|
||||||
this.scanningThread.start();
|
this.scanningThread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,24 +108,9 @@ public class DeviceAddFragment extends Fragment implements PreviewCallback {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.scannerView.onResume();
|
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() {
|
public ImageView getDevicesImage() {
|
||||||
return devicesImage;
|
return devicesImage;
|
||||||
|
@ -138,83 +118,11 @@ public class DeviceAddFragment extends Fragment implements PreviewCallback {
|
||||||
|
|
||||||
public void setScanListener(ScanListener scanListener) {
|
public void setScanListener(ScanListener scanListener) {
|
||||||
this.scanListener = scanListener;
|
this.scanListener = scanListener;
|
||||||
}
|
|
||||||
|
|
||||||
private class ScanningThread extends Thread {
|
if (this.scanningThread != null) {
|
||||||
|
this.scanningThread.setScanListener(scanListener);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
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();
|
|
||||||
|
|
||||||
}
|
|
|
@ -29,7 +29,9 @@ import android.widget.TextView;
|
||||||
import org.thoughtcrime.securesms.color.MaterialColor;
|
import org.thoughtcrime.securesms.color.MaterialColor;
|
||||||
import org.thoughtcrime.securesms.color.MaterialColors;
|
import org.thoughtcrime.securesms.color.MaterialColors;
|
||||||
import org.thoughtcrime.securesms.components.AvatarImageView;
|
import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||||
|
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||||
|
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||||
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.VibrateState;
|
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.jobs.MultiDeviceContactUpdateJob;
|
||||||
import org.thoughtcrime.securesms.preferences.AdvancedRingtonePreference;
|
import org.thoughtcrime.securesms.preferences.AdvancedRingtonePreference;
|
||||||
import org.thoughtcrime.securesms.preferences.ColorPreference;
|
import org.thoughtcrime.securesms.preferences.ColorPreference;
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
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
|
public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActivity implements Recipients.RecipientsModifiedListener
|
||||||
{
|
{
|
||||||
|
@ -179,8 +192,9 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
|
||||||
|
|
||||||
private final Handler handler = new Handler();
|
private final Handler handler = new Handler();
|
||||||
|
|
||||||
private Recipients recipients;
|
private Recipients recipients;
|
||||||
private BroadcastReceiver staleReceiver;
|
private BroadcastReceiver staleReceiver;
|
||||||
|
private MasterSecret masterSecret;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle icicle) {
|
public void onCreate(Bundle icicle) {
|
||||||
|
@ -189,6 +203,8 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
|
||||||
addPreferencesFromResource(R.xml.recipient_preferences);
|
addPreferencesFromResource(R.xml.recipient_preferences);
|
||||||
initializeRecipients();
|
initializeRecipients();
|
||||||
|
|
||||||
|
this.masterSecret = getArguments().getParcelable("master_secret");
|
||||||
|
|
||||||
this.findPreference(PREFERENCE_TONE)
|
this.findPreference(PREFERENCE_TONE)
|
||||||
.setOnPreferenceChangeListener(new RingtoneChangeListener());
|
.setOnPreferenceChangeListener(new RingtoneChangeListener());
|
||||||
this.findPreference(PREFERENCE_VIBRATE)
|
this.findPreference(PREFERENCE_VIBRATE)
|
||||||
|
@ -199,8 +215,6 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
|
||||||
.setOnPreferenceClickListener(new BlockClickedListener());
|
.setOnPreferenceClickListener(new BlockClickedListener());
|
||||||
this.findPreference(PREFERENCE_COLOR)
|
this.findPreference(PREFERENCE_COLOR)
|
||||||
.setOnPreferenceChangeListener(new ColorChangeListener());
|
.setOnPreferenceChangeListener(new ColorChangeListener());
|
||||||
this.findPreference(PREFERENCE_IDENTITY)
|
|
||||||
.setOnPreferenceClickListener(new IdentityClickedListener());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -245,7 +259,7 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
|
||||||
ListPreference vibratePreference = (ListPreference) this.findPreference(PREFERENCE_VIBRATE);
|
ListPreference vibratePreference = (ListPreference) this.findPreference(PREFERENCE_VIBRATE);
|
||||||
ColorPreference colorPreference = (ColorPreference) this.findPreference(PREFERENCE_COLOR);
|
ColorPreference colorPreference = (ColorPreference) this.findPreference(PREFERENCE_COLOR);
|
||||||
Preference blockPreference = this.findPreference(PREFERENCE_BLOCK);
|
Preference blockPreference = this.findPreference(PREFERENCE_BLOCK);
|
||||||
Preference identityPreference = this.findPreference(PREFERENCE_IDENTITY);
|
final Preference identityPreference = this.findPreference(PREFERENCE_IDENTITY);
|
||||||
|
|
||||||
mutePreference.setChecked(recipients.isMuted());
|
mutePreference.setChecked(recipients.isMuted());
|
||||||
|
|
||||||
|
@ -281,6 +295,23 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
|
||||||
|
|
||||||
if (recipients.isBlocked()) blockPreference.setTitle(R.string.RecipientPreferenceActivity_unblock);
|
if (recipients.isBlocked()) blockPreference.setTitle(R.string.RecipientPreferenceActivity_unblock);
|
||||||
else blockPreference.setTitle(R.string.RecipientPreferenceActivity_block);
|
else blockPreference.setTitle(R.string.RecipientPreferenceActivity_block);
|
||||||
|
|
||||||
|
getRemoteIdentityKey(getActivity(), masterSecret, recipients.getPrimaryRecipient()).addListener(new ListenableFuture.Listener<Optional<IdentityKey>>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Optional<IdentityKey> 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<Optional<IdentityKey>> getRemoteIdentityKey(final Context context,
|
||||||
|
final MasterSecret masterSecret,
|
||||||
|
final Recipient recipient)
|
||||||
|
{
|
||||||
|
final SettableFuture<Optional<IdentityKey>> future = new SettableFuture<>();
|
||||||
|
|
||||||
|
new AsyncTask<Recipient, Void, Optional<IdentityKey>>() {
|
||||||
|
@Override
|
||||||
|
protected Optional<IdentityKey> 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<IdentityKey> result) {
|
||||||
|
future.set(result);
|
||||||
|
}
|
||||||
|
}.execute(recipient);
|
||||||
|
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private class RingtoneChangeListener implements Preference.OnPreferenceChangeListener {
|
private class RingtoneChangeListener implements Preference.OnPreferenceChangeListener {
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||||
|
@ -413,10 +474,18 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
|
||||||
}
|
}
|
||||||
|
|
||||||
private class IdentityClickedListener implements Preference.OnPreferenceClickListener {
|
private class IdentityClickedListener implements Preference.OnPreferenceClickListener {
|
||||||
|
|
||||||
|
private final IdentityKey identityKey;
|
||||||
|
|
||||||
|
private IdentityClickedListener(IdentityKey identityKey) {
|
||||||
|
this.identityKey = identityKey;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
Intent verifyIdentityIntent = new Intent(getActivity(), VerifyIdentityActivity.class);
|
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);
|
startActivity(verifyIdentityIntent);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/**
|
/**
|
||||||
* Copyright (C) 2011 Whisper Systems
|
* Copyright (C) 2016 Open Whisper Systems
|
||||||
* Copyright (C) 2013 Open Whisper Systems
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -17,155 +16,360 @@
|
||||||
*/
|
*/
|
||||||
package org.thoughtcrime.securesms;
|
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.Bundle;
|
||||||
|
import android.os.Vibrator;
|
||||||
|
import android.support.annotation.DrawableRes;
|
||||||
import android.support.annotation.NonNull;
|
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.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.components.camera.CameraView;
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
|
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
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.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||||
import org.thoughtcrime.securesms.util.Hex;
|
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||||
import org.whispersystems.libsignal.SignalProtocolAddress;
|
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.IdentityKey;
|
||||||
import org.whispersystems.libsignal.state.SessionRecord;
|
import org.whispersystems.libsignal.fingerprint.Fingerprint;
|
||||||
import org.whispersystems.libsignal.state.SessionStore;
|
import org.whispersystems.libsignal.fingerprint.FingerprintIdentifierMismatchException;
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
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.
|
* Activity for verifying identity keys.
|
||||||
*
|
*
|
||||||
* @author Moxie Marlinspike
|
* @author Moxie Marlinspike
|
||||||
*/
|
*/
|
||||||
public class VerifyIdentityActivity extends KeyScanningActivity {
|
public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity implements ScanListener, View.OnClickListener {
|
||||||
|
|
||||||
private Recipient recipient;
|
private static final String TAG = VerifyIdentityActivity.class.getSimpleName();
|
||||||
private MasterSecret masterSecret;
|
|
||||||
|
|
||||||
private TextView localIdentityFingerprint;
|
public static final String RECIPIENT_ID = "recipient_id";
|
||||||
private TextView remoteIdentityFingerprint;
|
public static final String RECIPIENT_IDENTITY = "recipient_identity";
|
||||||
|
|
||||||
|
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
|
@Override
|
||||||
protected void onCreate(Bundle state, @NonNull MasterSecret masterSecret) {
|
protected void onCreate(Bundle state, @NonNull MasterSecret masterSecret) {
|
||||||
this.masterSecret = masterSecret;
|
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
getSupportActionBar().setTitle(R.string.AndroidManifest__verify_identity);
|
getSupportActionBar().setTitle(R.string.AndroidManifest__verify_identity);
|
||||||
|
|
||||||
setContentView(R.layout.verify_identity_activity);
|
Recipient recipient = RecipientFactory.getRecipientForId(this, getIntent().getLongExtra(RECIPIENT_ID, -1), true);
|
||||||
|
|
||||||
this.localIdentityFingerprint = (TextView)findViewById(R.id.you_read);
|
Bundle extras = new Bundle();
|
||||||
this.remoteIdentityFingerprint = (TextView)findViewById(R.id.friend_reads);
|
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));
|
||||||
|
|
||||||
|
scanFragment.setScanListener(this);
|
||||||
|
displayFragment.setClickListener(this);
|
||||||
|
|
||||||
|
initFragment(android.R.id.content, displayFragment, masterSecret, dynamicLanguage.getCurrentLocale(), extras);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
super.onResume();
|
switch (item.getItemId()) {
|
||||||
|
case android.R.id.home: finish(); return true;
|
||||||
this.recipient = RecipientFactory.getRecipientForId(this, this.getIntent().getLongExtra("recipient", -1), true);
|
|
||||||
|
|
||||||
initializeFingerprints();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeFingerprints() {
|
|
||||||
if (!IdentityKeyUtil.hasIdentityKey(this)) {
|
|
||||||
localIdentityFingerprint.setText(R.string.VerifyIdentityActivity_you_do_not_have_an_identity_key);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
localIdentityFingerprint.setText(Hex.toString(IdentityKeyUtil.getIdentityKey(this).serialize()));
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
IdentityKey identityKey = getRemoteIdentityKey(masterSecret, recipient);
|
@Override
|
||||||
|
public void onQrDataFound(final String data) {
|
||||||
|
Util.runOnMain(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
((Vibrator)getSystemService(Context.VIBRATOR_SERVICE)).vibrate(50);
|
||||||
|
|
||||||
if (identityKey == null) {
|
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
|
||||||
remoteIdentityFingerprint.setText(R.string.VerifyIdentityActivity_recipient_has_no_identity_key);
|
transaction.setCustomAnimations(R.anim.slide_from_bottom, R.anim.slide_to_top);
|
||||||
} else {
|
|
||||||
remoteIdentityFingerprint.setText(Hex.toString(identityKey.serialize()));
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle bundle) {
|
||||||
|
super.onCreate(bundle);
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
setFingerprintViews(fingerprint);
|
||||||
|
|
||||||
|
if (animateSuccessOnDraw) {
|
||||||
|
animateSuccessOnDraw = false;
|
||||||
|
animateVerifiedSuccess();
|
||||||
|
} else if (animateFailureOnDraw) {
|
||||||
|
animateFailureOnDraw = false;
|
||||||
|
animateVerifiedFailure();;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClickListener(View.OnClickListener listener) {
|
||||||
|
this.clickListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFingerprintViews(Fingerprint fingerprint) {
|
||||||
|
String digits = fingerprint.getDisplayableFingerprint().getDisplayText();
|
||||||
|
int partSize = digits.length() / codes.length;
|
||||||
|
|
||||||
|
for (int i=0;i<codes.length;i++) {
|
||||||
|
codes[i].setText(digits.substring(i * partSize, (i * partSize) + partSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] qrCodeData = fingerprint.getScannableFingerprint().getSerialized();
|
||||||
|
String qrCodeString = new String(qrCodeData, Charset.forName("ISO-8859-1"));
|
||||||
|
Bitmap qrCodeBitmap = QrCode.create(qrCodeString);
|
||||||
|
|
||||||
|
qrCode.setImageBitmap(qrCodeBitmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bitmap createVerifiedBitmap(int width, int height, @DrawableRes int id) {
|
||||||
|
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||||
|
Canvas canvas = new Canvas(bitmap);
|
||||||
|
Bitmap check = BitmapFactory.decodeResource(getResources(), id);
|
||||||
|
float offset = (width - check.getWidth()) / 2;
|
||||||
|
|
||||||
|
canvas.drawBitmap(check, offset, offset, null);
|
||||||
|
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void animateVerifiedSuccess() {
|
||||||
|
Bitmap qrBitmap = ((BitmapDrawable)qrCode.getDrawable()).getBitmap();
|
||||||
|
Bitmap qrSuccess = createVerifiedBitmap(qrBitmap.getWidth(), qrBitmap.getHeight(), R.drawable.ic_check_white_48dp);
|
||||||
|
|
||||||
|
qrVerified.setImageBitmap(qrSuccess);
|
||||||
|
qrVerified.getBackground().setColorFilter(getResources().getColor(R.color.green_500), PorterDuff.Mode.MULTIPLY);
|
||||||
|
|
||||||
|
animateVerified();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void animateVerifiedFailure() {
|
||||||
|
Bitmap qrBitmap = ((BitmapDrawable)qrCode.getDrawable()).getBitmap();
|
||||||
|
Bitmap qrSuccess = createVerifiedBitmap(qrBitmap.getWidth(), qrBitmap.getHeight(), R.drawable.ic_close_white_48dp);
|
||||||
|
|
||||||
|
qrVerified.setImageBitmap(qrSuccess);
|
||||||
|
qrVerified.getBackground().setColorFilter(getResources().getColor(R.color.red_500), PorterDuff.Mode.MULTIPLY);
|
||||||
|
|
||||||
|
animateVerified();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void animateVerified() {
|
||||||
|
ScaleAnimation scaleAnimation = new ScaleAnimation(0, 1, 0, 1,
|
||||||
|
ScaleAnimation.RELATIVE_TO_SELF, 0.5f,
|
||||||
|
ScaleAnimation.RELATIVE_TO_SELF, 0.5f);
|
||||||
|
scaleAnimation.setInterpolator(new OvershootInterpolator());
|
||||||
|
scaleAnimation.setDuration(800);
|
||||||
|
scaleAnimation.setAnimationListener(new Animation.AnimationListener() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationStart(Animation animation) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animation animation) {
|
||||||
|
qrVerified.postDelayed(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
ScaleAnimation scaleAnimation = new ScaleAnimation(1, 0, 1, 0,
|
||||||
|
ScaleAnimation.RELATIVE_TO_SELF, 0.5f,
|
||||||
|
ScaleAnimation.RELATIVE_TO_SELF, 0.5f);
|
||||||
|
|
||||||
|
scaleAnimation.setInterpolator(new AnticipateInterpolator());
|
||||||
|
scaleAnimation.setDuration(500);
|
||||||
|
ViewUtil.animateOut(qrVerified, scaleAnimation, View.GONE);
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationRepeat(Animation animation) {}
|
||||||
|
});
|
||||||
|
|
||||||
|
ViewUtil.animateIn(qrVerified, scaleAnimation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public static class VerifyScanFragment extends Fragment {
|
||||||
protected void initiateDisplay() {
|
|
||||||
if (!IdentityKeyUtil.hasIdentityKey(this)) {
|
private View container;
|
||||||
Toast.makeText(this,
|
private CameraView cameraView;
|
||||||
R.string.VerifyIdentityActivity_you_don_t_have_an_identity_key_exclamation,
|
private ScanningThread scanningThread;
|
||||||
Toast.LENGTH_LONG).show();
|
private ScanListener scanListener;
|
||||||
return;
|
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
|
||||||
|
this.container = ViewUtil.inflate(inflater, viewGroup, R.layout.verify_scan_fragment);
|
||||||
|
this.cameraView = ViewUtil.findById(container, R.id.scanner);
|
||||||
|
|
||||||
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
super.initiateDisplay();
|
@Override
|
||||||
}
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
@Override
|
this.scanningThread = new ScanningThread();
|
||||||
protected void initiateScan() {
|
this.scanningThread.setScanListener(scanListener);
|
||||||
IdentityKey identityKey = getRemoteIdentityKey(masterSecret, recipient);
|
this.scanningThread.setCharacterSet("ISO-8859-1");
|
||||||
|
this.cameraView.onResume();
|
||||||
if (identityKey == null) {
|
this.cameraView.setPreviewCallback(scanningThread);
|
||||||
Toast.makeText(this, R.string.VerifyIdentityActivity_recipient_has_no_identity_key_exclamation,
|
this.scanningThread.start();
|
||||||
Toast.LENGTH_LONG).show();
|
|
||||||
} else {
|
|
||||||
super.initiateScan();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getScanString() {
|
|
||||||
return getString(R.string.VerifyIdentityActivity_scan_contacts_qr_code);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getDisplayString() {
|
|
||||||
return getString(R.string.VerifyIdentityActivity_display_your_qr_code);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected IdentityKey getIdentityKeyToCompare() {
|
|
||||||
return getRemoteIdentityKey(masterSecret, recipient);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected IdentityKey getIdentityKeyToDisplay() {
|
|
||||||
return IdentityKeyUtil.getIdentityKey(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getNotVerifiedMessage() {
|
|
||||||
return getString(R.string.VerifyIdentityActivity_warning_the_scanned_key_does_not_match_please_check_the_fingerprint_text_carefully);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getNotVerifiedTitle() {
|
|
||||||
return getString(R.string.VerifyIdentityActivity_not_verified_exclamation);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getVerifiedMessage() {
|
|
||||||
return getString(R.string.VerifyIdentityActivity_their_key_is_correct_it_is_also_necessary_to_verify_your_key_with_them_as_well);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getVerifiedTitle() {
|
|
||||||
return getString(R.string.VerifyIdentityActivity_verified_exclamation);
|
|
||||||
}
|
|
||||||
|
|
||||||
private @Nullable IdentityKey getRemoteIdentityKey(MasterSecret masterSecret, Recipient recipient) {
|
|
||||||
IdentityKeyParcelable identityKeyParcelable = getIntent().getParcelableExtra("remote_identity");
|
|
||||||
|
|
||||||
if (identityKeyParcelable != null) {
|
|
||||||
return identityKeyParcelable.get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SessionStore sessionStore = new TextSecureSessionStore(this, masterSecret);
|
@Override
|
||||||
SignalProtocolAddress axolotlAddress = new SignalProtocolAddress(recipient.getNumber(), SignalServiceAddress.DEFAULT_DEVICE_ID);
|
public void onPause() {
|
||||||
SessionRecord record = sessionStore.loadSession(axolotlAddress);
|
super.onPause();
|
||||||
|
this.cameraView.onPause();
|
||||||
if (record == null) {
|
this.scanningThread.stopScanning();
|
||||||
return null;
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConfigurationChanged(Configuration newConfiguration) {
|
||||||
|
super.onConfigurationChanged(newConfiguration);
|
||||||
|
this.cameraView.onPause();
|
||||||
|
this.cameraView.onResume();
|
||||||
|
this.cameraView.setPreviewCallback(scanningThread);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScanListener(ScanListener listener) {
|
||||||
|
if (this.scanningThread != null) scanningThread.setScanListener(listener);
|
||||||
|
this.scanListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
return record.getSessionState().getRemoteIdentityKey();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,117 +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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
37
src/org/thoughtcrime/securesms/qr/QrCode.java
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
5
src/org/thoughtcrime/securesms/qr/ScanListener.java
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package org.thoughtcrime.securesms.qr;
|
||||||
|
|
||||||
|
public interface ScanListener {
|
||||||
|
public void onQrDataFound(String data);
|
||||||
|
}
|
125
src/org/thoughtcrime/securesms/qr/ScanningThread.java
Normal file
|
@ -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> scanListener = new AtomicReference<>();
|
||||||
|
private final Map<DecodeHintType, String> 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;
|
||||||
|
}
|
||||||
|
}
|