Compare commits

...

27 Commits
v0.7.2 ... v0.8

Author SHA1 Message Date
Moxie Marlinspike
d8cd09d46b Bumped version to 0.8 2012-11-03 14:36:49 -07:00
Moxie Marlinspike
09dcb9ac66 Updated translation files from Transifex 2012-10-31 18:18:45 -07:00
Moxie Marlinspike
acffda1ab8 formatting 2012-10-31 18:03:09 -07:00
Moxie Marlinspike
6b18f66e19 disambiguate send 2012-10-31 18:00:59 -07:00
Moxie Marlinspike
f26dbb2f34 disambiguate 2012-10-31 17:56:39 -07:00
Moxie Marlinspike
93c0fffc31 We need to force plaintext on key exchange messages. 2012-10-29 20:53:04 -07:00
Moxie Marlinspike
c5ed820590 Create DB indexes for optimized queries. 2012-10-29 18:01:09 -07:00
Moxie Marlinspike
3a9908b40d formatting 2012-10-29 17:34:14 -07:00
Moxie Marlinspike
187ec95817 Add collating support for group SMS/MMS messages.
1) When sending an SMS or MMS to multiple recipients, only show one
ConversationItem, but provide statistics on the number of recipients
delivered to.

2) Still break up the messages for secure and insecure messages.
2012-10-29 16:51:42 -07:00
Moxie Marlinspike
3a8d29e279 Major reorganization of view/model interactions.
Mostly, the inheritance graph for MessageRecord/MmsMessageRecord was
all messed up, and each class was overloaded for things it shouldn't
have been.

1) Broke MessageRecord/MmsMessageRecord up into: DisplayRecord, ThreadRecord,
MessageRecord, SmsMessageRecord, NotificationMmsMessageRecord, and
MediaMmsMessageRecord.

2) Updated all the adapters/views to keep pace with that change.
2012-10-28 18:24:21 -07:00
Moxie Marlinspike
0b3e939ac8 Improve support for "me" contact.
1) Add >= ICS profile support (the system-supported "me" contact).

2) Improve <= Gingerbread support for me contact by auto-detecting
contacts that have the same number as the SIM card.

3) Tie in identity key import/export support to the "me" contact.

4) Don't display a "me" selection option in preference if it can
be auto-detected.

5) Refactor out the ContactAccessorNewApi back into the base class.
2012-10-22 19:17:08 -07:00
Moxie Marlinspike
c13a3a8181 Fix "Group Threads" so that messages are encrypted when possible.
1) Change the MessageSender logic so that individual SMS messages
are encrypted whenever there is a secure session, unless the UI
explicitly specifies otherwise.

2) Change the MMS logic so that messages to a recipient with a
secure session are all sent individually, instead of including
those recipients into the batch plaintext message.
2012-10-21 17:41:44 -07:00
Moxie Marlinspike
1bd260b981 Improve UI for group SMS ConversationActivity
1) Change title to indicate it's a group message, and specify
the number of recipients.

2) Add an ActionBar icon to display a list of the recipients.
2012-10-21 14:34:09 -07:00
Moxie Marlinspike
05ce6d6cac Added Play Store description to strings file (for translation). 2012-10-15 19:58:16 -07:00
Moxie Marlinspike
e29993e48f Bump version to 0.7.4 2012-10-15 19:56:12 -07:00
Moxie Marlinspike
f2aa393974 Updated completed language translations. 2012-10-15 18:31:15 -07:00
Moxie Marlinspike
2c82e7b343 Updated PDU processing code.
The code we use for PDU parsing and composing comes straight from
the Android framework, but it's an internal API, so we duplicate
the code here.  These changes represent updates that have been
made as of the JB release.
2012-10-13 10:33:56 -07:00
Moxie Marlinspike
7beb8e489b Merge pull request #76 from thoughtbox/Localisation-change
Support translation of 'FWD' prefix.
2012-10-12 19:22:36 -07:00
Tor Houghton
73bde7accd Localisation change
Localisation of the message prefix when forwarding a message (in
Norwegian, this would be "VS:" for instance).
2012-10-12 12:00:34 +02:00
Tor Houghton
192ffb4d2f Ignore .DS_Store 2012-10-12 11:55:43 +02:00
Moxie Marlinspike
82f8ba3584 Removed tabs 2012-09-30 19:56:29 -07:00
Moxie Marlinspike
030b39cd9c Updated attachment selection dialog.
1) Added nice-looking holo-themed graphics for each attachment type.

2) Removed old un-scaled graphics.

3) Stringified the attachment types.
2012-09-30 18:58:40 -07:00
Moxie Marlinspike
3cf77b6fd0 Display contact icons correctly in MMS ConversationItems 2012-09-30 12:28:03 -07:00
Moxie Marlinspike
cf9dc51f31 Initial fixes for MMS retrieval.
1) Parse the APN information based on what the ConnectionManager
tells us.

2) Accept email addresses as a valid Recipient format.
2012-09-30 11:46:45 -07:00
Moxie Marlinspike
59e7226183 Support ant signing builds 2012-09-29 16:14:13 -07:00
Moxie Marlinspike
f743af32de javac wants us to be more explicit 2012-09-28 16:11:11 -07:00
Moxie Marlinspike
090068ea66 Update version to 0.7.3 for repackaging. 2012-09-28 14:31:15 -07:00
121 changed files with 4180 additions and 2381 deletions

3
.gitignore vendored
View File

@@ -9,3 +9,6 @@ TextSecure.iml
out
tests
lint.xml
local.properties
ant.properties
.DS_Store

View File

@@ -1,32 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.thoughtcrime.securesms"
android:versionCode="27"
android:versionName="0.7.2">
android:versionCode="30"
android:versionName="0.8">
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="16"/>
<permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"
android:label="Access to TextSecure Secrets"
android:protectionLevel="signature" />
android:label="Access to TextSecure Secrets"
android:protectionLevel="signature" />
<uses-permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"></uses-permission>
<uses-permission android:name="android.permission.BROADCAST_WAP_PUSH"></uses-permission>
<uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission>
<uses-permission android:name="android.permission.WRITE_CONTACTS"></uses-permission>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.RECEIVE_SMS"></uses-permission>
<uses-permission android:name="android.permission.RECEIVE_MMS"></uses-permission>
<uses-permission android:name="android.permission.READ_SMS"></uses-permission>
<uses-permission android:name="android.permission.SEND_SMS"></uses-permission>
<uses-permission android:name="android.permission.WRITE_SMS"></uses-permission>
<uses-permission android:name="android.permission.VIBRATE"></uses-permission>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_PROFILE"></uses-permission>
<uses-permission android:name="android.permission.WRITE_PROFILE"></uses-permission>
<uses-permission android:name="android.permission.BROADCAST_WAP_PUSH"></uses-permission>
<uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission>
<uses-permission android:name="android.permission.WRITE_CONTACTS"></uses-permission>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.RECEIVE_SMS"></uses-permission>
<uses-permission android:name="android.permission.RECEIVE_MMS"></uses-permission>
<uses-permission android:name="android.permission.READ_SMS"></uses-permission>
<uses-permission android:name="android.permission.SEND_SMS"></uses-permission>
<uses-permission android:name="android.permission.WRITE_SMS"></uses-permission>
<uses-permission android:name="android.permission.VIBRATE"></uses-permission>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application android:icon="@drawable/icon"
android:label="@string/app_name"

25
build.xml Normal file
View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="TextSecure" default="help">
<property file="local.properties" />
<property file="ant.properties" />
<property environment="env" />
<condition property="sdk.dir" value="${env.ANDROID_HOME}">
<isset property="env.ANDROID_HOME" />
</condition>
<loadproperties srcFile="project.properties" />
<fail
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
unless="sdk.dir"
/>
<import file="custom_rules.xml" optional="true" />
<!-- version-tag: 1 -->
<import file="${sdk.dir}/tools/ant/build.xml" />
</project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 869 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 723 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -77,7 +77,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="Download"
android:text="@string/conversation_item_received__download"
android:visibility="gone" />
<TextView android:id="@+id/mms_label_downloading"
@@ -86,21 +86,39 @@
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:gravity="center"
android:text="Downloading"
android:text="@string/conversation_item_received__downloading"
android:visibility="gone" />
</LinearLayout>
<TextView android:id="@+id/conversation_item_date"
android:autoLink="all"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:linksClickable="false"
android:textAppearance="?android:attr/textAppearanceSmall"
android:gravity="left"
android:textColor="#ffcccccc"
android:paddingTop="1dip"/>
<LinearLayout android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="left">
<TextView android:id="@+id/group_message_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:linksClickable="false"
android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_gravity="left"
android:textColor="#ffcccccc"
android:visibility="gone"
android:layout_marginRight="8dip"
android:paddingTop="1dip"/>
<TextView android:id="@+id/conversation_item_date"
android:autoLink="all"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:linksClickable="false"
android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_gravity="left"
android:textColor="#ffcccccc"
android:paddingTop="1dip"/>
</LinearLayout>
</LinearLayout>
<LinearLayout android:id="@+id/indicators_parent"

View File

@@ -99,7 +99,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="Download"
android:text="@string/conversation_item_sent__download"
android:visibility="gone" />
<TextView android:id="@+id/mms_label_downloading"
@@ -108,21 +108,40 @@
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:gravity="center"
android:text="Downloading"
android:text="@string/conversation_item_sent__downloading"
android:visibility="gone" />
</LinearLayout>
<TextView android:id="@+id/conversation_item_date"
android:autoLink="all"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:linksClickable="false"
android:textAppearance="?android:attr/textAppearanceSmall"
android:gravity="right"
android:textColor="#ffcccccc"
android:paddingTop="1dip"/>
<LinearLayout android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="right">
<TextView android:id="@+id/group_message_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:linksClickable="false"
android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_gravity="right"
android:textColor="#ffcccccc"
android:visibility="gone"
android:layout_marginRight="8dip"
android:paddingTop="1dip"/>
<TextView android:id="@+id/conversation_item_date"
android:autoLink="all"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:linksClickable="false"
android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_gravity="right"
android:textColor="#ffcccccc"
android:paddingTop="1dip"/>
</LinearLayout>
</LinearLayout>
<view xmlns:android="http://schemas.android.com/apk/res/android"

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:title="@string/convesation_group_options__recipients_list"
android:id="@+id/menu_group_recipients"
android:icon="@drawable/ic_groups_holo_dark"
android:showAsAction="ifRoom" />
</menu>

View File

@@ -35,6 +35,7 @@
<string name="ApplicationPreferenceActivity_you_need_to_have_entered_your_passphrase_before_importing_keys">يجب عليك إدخال العبارة السرية قبل استيراد المفاتيح السرية</string>
<string name="ApplicationPreferenceActivity_you_need_to_have_entered_your_passphrase_before_managing_keys">يجب عليك إدخال العبارة السرية قبل ضبط المفاتيح السرية</string>
<string name="ApplicationPreferenceActivity_you_havent_set_a_passphrase_yet">لم تقم بتحديد عبارة سرية بعد!</string>
<!--AttachmentTypeSelectorAdapter-->
<!--ConversationItem-->
<string name="ConversationItem_message_size_d_kb">حجم الرسالة: %d كيلوبايت</string>
<string name="ConversationItem_expires_s">تاريخ الانتهاء: %s</string>
@@ -338,6 +339,7 @@
<string name="conversation__menu_add_attachment">إضافات</string>
<string name="conversation__menu_delete_thread">مسح المحاذفة</string>
<string name="conversation__menu_compare">قارن</string>
<!--conversation_group_options-->
<!--key_scanning-->
<string name="key_scanning__menu_compare">قارن</string>
<string name="key_scanning__menu_get_scanned_to_compare">قم بالمسح الضوئي للمقارنة</string>
@@ -353,5 +355,8 @@
<string name="text_secure_normal__menu_clear_passphrase">مسح العبارة السرية</string>
<!--verify_keys-->
<string name="verify_keys__menu_verified">تم التحقق!</string>
<!--Misc. piggybacking-->
<!--EOF-->
<string name="conversation_item_sent__download">تحميل</string>
<string name="conversation_item_received__download">تحميل</string>
</resources>

353
res/values-bo/strings.xml Normal file
View File

@@ -0,0 +1,353 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="app_name">འཕྲིན་ཐུང་བདེ་འཇགས།</string>
<string name="yes">གཏན་འཁེལ།</string>
<string name="no">རྩིས་མེད་གཏོང་།</string>
<string name="delete">སུབ།</string>
<!--ApplicationExportManager-->
<string name="ApplicationExportManager_import_database_and_settings_title">གཞི་གྲངས་དང་སྒྲིག་བཟོ་རྣམས་ནང་འཇུག་བྱེད་རྒྱུ་ཡིན་ནམ།</string>
<string name="ApplicationExportManager_import_database_and_settings_message">SD Card ནས་བདེ་འཇགས་འཕྲིན་ཐུང་གི་གཞི་གྲངས། ལྡེ་མིག་དང་སྒྲིག་བཟོ་རྣམས་ནང་འཇུག་བྱེད་རྒྱུ་ཡིན་ནམ།\nཉེན་བརྡ།: འདི་ཡིས་ད་ཡོད་འཕྲིན་ཐུང་དང་ལྡེ་མིག སྒྲིག་བཟོ་སོགས་གསུབ་གི་རེད།</string>
<string name="ApplicationExportManager_importing_database_and_keys">གཞི་གྲངས་དང་ལྡེ་མིག་རྣམས་ནང་འཇུག་བྱེད་བཞིན་ཡོད།</string>
<string name="ApplicationExportManager_importing_your_sms_database_keys_and_settings">ཁྱོད་ཀྱི་འཕྲིན་ཐུང་གི་གཞི་གྲངས། ལྡེ་མིག་དང་སྒྲིག་བཟོ་རྣམས་ནང་འཇུག་བྱེད་བཞིན་ཡོད།</string>
<string name="ApplicationExportManager_export_database_question">གཞི་གྲངས་ཕྱིར་འདོན་བྱེད་རྒྱུ་ཡིན་ནམ།</string>
<string name="ApplicationExportManager_export_textsecure_database_keys_and_settings_prompt">SD Card ནས་བདེ་འཇགས་འཕྲིན་ཐུང་གི་གཞི་གྲངས། ལྡེ་མིག་དང་སྒྲིག་བཟོ་རྣམས་ཕྱིར་འདོན་བྱེད་རྒྱུ་ཡིན་ནམ།</string>
<string name="ApplicationExportManager_exporting_database_and_keys">གཞི་གྲངས་དང་ལྡེ་མིག་ཕྱིར་འདོན་བྱེད་བཞིན་ཡོད།</string>
<string name="ApplicationExportManager_exporting_your_sms_database_keys_and_settings">ཁྱོད་ཀྱི་འཕྲིན་ཐུང་གི་གཞི་གྲངས། ལྡེ་མིག་དང་སྒྲིག་བཟོ་རྣམས་ཕྱིར་འདོན་བྱེད་བཞིན་ཡོད།</string>
<string name="ApplicationExportManager_no_sd_card_found_exclamation">SD Card མི་འདུག</string>
<string name="ApplicationExportManager_error_exporting_to_sd_exclamation">SD Card ཕྱིར་འདོན་བྱེད་སྐབས་ནོར་འཁྲུལ་བྱུང་།</string>
<string name="ApplicationExportManager_import_successful_exclamation">ནང་འཇུག་གྲུབ་འབྲས་ལེགས་པོ་བྱུང་།</string>
<string name="ApplicationExportManager_export_successful_exclamation">ཕྱིར་འདོན་གྲུབ་འབྲས་ལེགས་པོ་བྱུང་།</string>
<string name="ApplicationExportManager_import">ནང་འཇུག་བྱོས།</string>
<string name="ApplicationExportManager_export">ཕྱིར་འདོན་བྱེད།</string>
<!--ApplicationMigrationManager-->
<string name="ApplicationMigrationManager_migrating_database">གཞི་གྲངས་གནས་སྤོས་བྱེད་བཞིན་་ཡོད།</string>
<string name="ApplicationMigrationManager_migrating_text_message_database">འཕྲིན་ཐུང་གི་གཞི་གྲངས་གནས་སྤོས་བྱེད་བཞིན་་ཡོད།</string>
<string name="ApplicationMigrationManager_copy_system_text_message_database_question">མ་ལག་འཕྲིན་ཐུང་གི་གཞི་གྲངས་དེ་འདྲ་བཟོ་བྱེད་རྒྱུ་ཡིན་ནམ།</string>
<string name="ApplicationMigrationManager_copy_system_text_message_database_explanation">ཡི་གེའི་ཉེན་སྲུང་གི་པར་གཞི་དེས། གཏན་འཇགས་མ་ལག་གཞི་གྲངས་མཛོད་ལས་ལོགས་སུ་ཡིན་པའི་གསང་སྡོམ་གྱི་གཞི་གྲངས་མཛོད་ཞིག་བེད་སྤྱོད་གཏོང་གི་ཡོད། ཁྱེད་རང་སོ་སོའི་ད་ཡོད་འཕྲིན་ཐུང་རྣམས་བདེ་འཇགས་འཕྲིན་ཐུང་གི་གསང་སྡོམ་གྱི་གཞི་གྲངས་མཛོད་དེའི་ཁོངས་སུ་བཤུ་ན་འདོད་དམ། ཁྱེད་རང་གི་གཏན་འཇགས་མ་ལག་གི་གཞི་གྲངས་མཛོད་དེ་ནུས་མེད་ཆགས་གི་མ་རེད།</string>
<string name="ApplicationMigrationManager_copy">འདྲ་བཟོ་བྱེད།</string>
<string name="ApplicationMigrationManager_dont_copy">འདྲ་བཟོ་མ་བྱེད།</string>
<!--ApplicationPreferencesActivity-->
<string name="ApplicationPreferenceActivity_not_found_exclamation">རྙེད་མ་སོང་། </string>
<string name="ApplicationPreferenceActivity_no_valid_identity_key_was_found_in_the_specified_contact">འགྲེལ་བརྗོད་བྱས་པའི་འབྲེལ་གཏུག་ནས་ཁུངས་ཐུབ་པའི་ངོས་འཛིན་བྱེད་ཀྱི་ལྡེ་མིག་མི་རྙེད།</string>
<string name="ApplicationPreferenceActivity_you_don_t_have_an_identity_key_exclamation">ཁྱེད་ལ་ངོས་འཛིན་བྱེད་ཀྱི་ལྡེ་མིག་མི་འདུག</string>
<string name="ApplicationPreferenceActivity_you_have_not_yet_defined_a_contact_for_yourself">ཁྱེད་ཀྱིས་འབྲེལ་ལམ་བྱ་ཡུལ་གསལ་བཤད་བྱས་མི་འདུག སྒྲིག་བཀོད་འདེམ་ཐོ་ལས་བདམས་པར་ཞུ༏</string>
<string name="ApplicationPreferenceActivity_exported_to_contacts_database">འབྲེལ་མིང་ཆ་ཚང་གཞི་གྲངས་མཛོད་ནང་ཕྱིར་འདོན་བྱས།</string>
<string name="ApplicationPreferenceActivity_you_need_to_have_entered_your_passphrase_before_importing_keys">ཁྱོད་ཀྱིས་ལྡེ་མིག་ནང་འཇུག་མ་བྱས་སྔོན་ལ་གསང་གྲངས་འཇོག་དགོས།</string>
<string name="ApplicationPreferenceActivity_you_need_to_have_entered_your_passphrase_before_managing_keys">ཁྱོད་ཀྱིས་ལྡེ་མིག་དོ་དམ་མ་བྱས་སྔོན་ལ་གསང་གྲངས་འཇོག་དགོས།</string>
<string name="ApplicationPreferenceActivity_you_havent_set_a_passphrase_yet">ཁྱོད་ཀྱིས་གསང་ཡིག་བཟོས་མི་འདུག</string>
<!--AttachmentTypeSelectorAdapter-->
<string name="AttachmentTypeSelectorAdapter_picture">འདྲ་པར།</string>
<string name="AttachmentTypeSelectorAdapter_video">བརྙན་པར།</string>
<!--ConversationItem-->
<string name="ConversationItem_message_size_d_kb">འཕྲིན་ཐུང་ཆ་ཚད།: %d KB</string>
<string name="ConversationItem_expires_s">དུས་ཚོད་རྫོགས།: %s</string>
<string name="ConversationItem_error_sending_message">གཏོང་བཞིན་པའི་འཕྲིན་ཐུང་དེར་ནོར་འཁྲུལ་ཞིག་འདུག </string>
<string name="ConversationItem_sending">གཏོང་བཞིན་ཡོད། </string>
<string name="ConversationItem_saving_attachment">ཟུར་སྣོན་ཉར་ཚགས་བྱེད་བཞིན་ཡོད།</string>
<string name="ConversationItem_saving_attachment_to_sd_card">SD card ནང་ཟུར་སྣོན་ཉར་ཚགས་བྱེད་བཞིན་ཡོད།</string>
<string name="ConversationItem_save_to_sd_card">SD card ནང་ཉར་ཚགས་བྱེད་རྒྱུ་ཡིན་ནམ།</string>
<string name="ConversationItem_this_media_has_been_stored_in_an_encrypted_database_warning">བརྒྱུད་ལམ་འདི་གཞི་གྲངས་མཛོད་ཀྱི་གསང་སྡོམ་ཁོངས་སུ་ཉར་ཡོད་། ཁྱེད་ནས་ SD card ནང་དུ་ཉར་ཚགས་བྱས་བའི་པར་གཞི་དེ་གསང་སྡོམ་བྱས་མི་འདུག ཁྱེད་ནས་མུ་མཐུད་ན་འདོད་དམ།</string>
<string name="ConversationItem_error_while_saving_attachment_to_sd_card">SD Card ནང་ཟུར་སྣོན་ཉར་ཚགས་བྱེད་སྐབས་ནོར་འཁྲུལ་བྱུང་།</string>
<string name="ConversationItem_success_exclamation">གྲུབ་འབྲས་ལེགས།</string>
<string name="ConversationItem_unable_to_write_to_sd_card_exclamation">SD Card ནང་འདྲ་བཤུས་བྱེད་མི་ཐུབ།</string>
<string name="ConversationItem_view_secure_media_question">ཉེན་སྲུང་ཡོད་པའི་སྨྱན་གཟུགས་ལ་བལྟ།</string>
<string name="ConversationItem_this_media_has_been_stored_in_an_encrypted_database_external_viewer_warning">བརྒྱུད་ལམ་འདི་གཞི་གྲངས་མཛོད་ཀྱི་གསང་སྡོམ་ཁོངས་སུ་ཉར་ཡོད། Unfortunately, to view it with an external content viewer currently requires the data to be temporarily decrypted and written to disk. Are you sure that you would like to do this?</string>
<string name="ConversationItem_key_exchange_message">ལྡེ་མིག་བརྗེ་ལེན་བྱེད་རྒྱུའི་བརྡ་ལན།</string>
<string name="ConversationItem_received_and_processed_key_exchange_message">ལྡེ་མིག་བརྗེ་ལེན་བྱེད་རྒྱུའི་བརྡ་ལན་འབྱོར་བ་དང་ལས་སྣོན་བྱས།</string>
<string name="ConversationItem_error_received_stale_key_exchange_message">ནོར་འཁྲུལ། ལྡེ་མིག་འབྱོར་བ་དེ་རྙིང་པ་རེད་འདུག</string>
<string name="ConversationItem_received_key_exchange_message_click_to_process">ལྡེ་མིག་བརྗེ་ལེན་བྱེད་རྒྱུའི་བརྡ་ལན་འབྱོར་བྱུང་། ལས་སྣོན་བྱ་རྒྱུའི་སྒང་གནོན།</string>
<!--ConversationActivity-->
<string name="ConversationActivity_initiate_secure_session_question">བདེ་འཇགས་ཀྱི་གཏམ་གླེང་འགོ་བརྩམ་གྱི་ཡིན་ནམ།</string>
<string name="ConversationActivity_initiate_secure_session_with_s_question">%s དང་ལྷན་དུ་བདེ་འཇགས་ཀྱི་གཏམ་གླེང་འགོ་བརྩམ་གྱི་ཡིན་ནམ།</string>
<string name="ConversationActivity_abort_secure_session_confirmation">བདེ་འཇགས་གཏམ་གླེང་གི་ར་སྤྲོད་མཚམས་འཇོག</string>
<string name="ConversationActivity_are_you_sure_that_you_want_to_abort_this_secure_session_question">ཁྱོད་ཀྱིས་བདེ་འཇགས་གཏམ་དངོས་སུ་གླེང་མཚམས་འཇོག་རྒྱུ་ཡིན་ནམ།</string>
<string name="ConversationActivity_delete_thread_confirmation">འཕྲིན་ཐུང་སྐུད་རིམ་ཆ་ཚང་སུབ་ན་སྒྲིག་ཀྱི་རེད་དམ།</string>
<string name="ConversationActivity_are_you_sure_that_you_want_to_permanently_delete_this_conversation_question">ཁྱོད་ཀྱིས་དངོས་གནས་འཕྲིན་ཐུང་སྐུད་རིམ་ཆ་ཚང་གཏན་དུ་གསུབ་རྒྱུ་ཡིན་ནམ།</string>
<string name="ConversationActivity_add_attachment">ཟུར་སྣོན་འཇོག</string>
<string name="ConversationActivity_compose_message">འཕྲིན་ཐུང་བྲིས།</string>
<string name="ConversationActivity_sorry_there_was_an_error_setting_your_attachment">དགོངས་དག ཁྱོད་ཀྱི་ཟུར་སྣོན་ལ་སྒྲིག་བཟོ་བྱེད་པར་ནོར་འཁྲུལ་ཞིག་འདུག</string>
<string name="ConversationActivity_sorry_the_selected_video_exceeds_message_size_restrictions">དགོངས་དག ཁྱོད་ཀྱིས་བདམས་པའི་བརྙན་རིས་དེ་གཏན་ཁེལ་བྱས་ཡོད་པའི་ཚད་ལས་བརྒལ་འདུག</string>
<string name="ConversationActivity_sorry_the_selected_audio_exceeds_message_size_restrictions">ཁྱོད་ཀྱིས་བདམས་པའི་སྒྲ་ཟློས་དེ་གཏན་ཁེལ་བྱས་ཡོད་པའི་ཚད་ལས་བརྒལ་འདུག</string>
<string name="ConversationActivity_recipient_is_not_a_valid_sms_or_email_address_exclamation">གཏོང་ས་དེ་གློག་འཕྲིན་ཁ་བྱང་ངམ་འཕྲིན་ཐུང་ཁ་བྱང་ཚད་ལྡན་མི་འདུག </string>
<string name="ConversationActivity_message_is_empty_exclamation">འཕྲིན་ཐུང་མི་འདུག</string>
<string name="ConversationActivity_group_conversation">མཉམ་འདུས་གླེང་མོལ། </string>
<!--ConversationFragment-->
<string name="ConversationFragment_message_details">འཕྲིན་ཐུང་ཞིབ་ཆ།</string>
<string name="ConversationFragment_sender_s_transport_s_sent_received_s">གཏོང་མཁན།:%1$s དབོར་བ།:%2$s བཏང་།/འབྱོར།:%3$s</string>
<string name="ConversationFragment_confirm_message_delete">འཕྲིན་ཐུང་སུབ་ན་སྒྲིག་ཀྱི་རེད་དམ།</string>
<string name="ConversationFragment_are_you_sure_you_want_to_permanently_delete_this_message">ཁྱོད་ཀྱིས་དངོས་གནས་འཕྲིན་ཐུང་འདི་གཏན་དུ་གསུབ་རྒྱུ་ཡིན་ནམ།</string>
<!--ConversationListAdapter-->
<string name="ConversationListAdapter_encrypted_message_enter_passphrase">གསང་སྡོམ་བྱེད་པའི་འཕྲིན་ཐུང་རེད། གསང་ཡིག་འཇོག</string>
<string name="ConversationListAdapter_key_exchange_message">ལྡེ་མིག་བརྗེ་ལེན་བྱེད་རྒྱུའི་བརྡ་ལན།</string>
<!--ConversationListFragment-->
<string name="ConversationListFragment_delete_threads_question">འཕྲིན་ཐུང་སྐུད་རིམ་གསུབ་རྒྱུ་ཡིན་ནམ།</string>
<string name="ConversationListFragment_are_you_sure_you_wish_to_delete_all_selected_conversation_threads">ཁྱོད་ཀྱིས་དངོས་གནས་འཕྲིན་ཐུང་སྐུད་རིམ་འདེམས་པ་ཆ་ཚང་གསུབ་རྒྱུ་ཡིན་ནམ།</string>
<!--ConversationListItem-->
<string name="ConversationListItem_key_exchange_message">ལྡེ་མིག་བརྗེ་ལེན་བྱེད་རྒྱུའི་བརྡ་ལན།</string>
<!--KeyScanningActivity-->
<string name="KeyScanningActivity_no_scanned_key_found_exclamation">ཞིབ་བརྟག་བྱེད་པའི་ལྡེ་མིག་རྙེད་མི་ཐུབ།</string>
<!--PassphraseChangeActivity-->
<string name="PassphraseChangeActivity_passphrases_dont_match_exclamation">གསང་ཡིག་མཚུངས་ཀྱི་མི་འདུག</string>
<string name="PassphraseChangeActivity_incorrect_old_passphrase_exclamation">གསང་ཡིག་རྙིང་པ་མ་དག་པ་དེ་འདིར་བྲིས།</string>
<!--PassphraseCreateActivity-->
<string name="PassphraseCreateActivity_passphrases_dont_match_exclamation">གསང་ཡིག་མཚུངས་ཀྱི་མི་འདུག</string>
<string name="PassphraseCreateActivity_generating_keypair">ལྡེ་མིག་གི་ཆ་བསྐྲུན་གྱི་ཡོད།</string>
<string name="PassphraseCreateActivity_generating_a_local_encryption_keypair">རང་སའི་གསང་སྡོམ་གྱི་ལྡེ་མིག་ཆ་ཞིག་སྐྱེད་སྒྲུབ།</string>
<!--PassphrasePromptActivity-->
<string name="PassphrasePromptActivity_invalid_passphrase_exclamation">གསང་ཡིག་ཁུངས་ལྡན་མིན།</string>
<!--ReceiveKeyActivity-->
<string name="ReceiveKeyActivity_error_you_have_received_a_corrupted_public_key">ནོར་འཁྲུལ།: ཁྱེད་ལ་མི་གཞན་ནས་ངན་ལྷད་ཅན་གྱི་ལྡེ་མིག་ཞིག་འབྱོར་འདུག ལྡེ་མིག་འདིས་ལས་སྣོན་བྱེད་མི་ཐུབ། བདེ་འཇགས་ཀྱི་གཏམ་གླེང་བསྐྱར་དུ་འགོ་བརྩམ་རོགས།</string>
<string name="ReceiveKeyActivity_error_you_have_received_a_public_key_from_an_unsupported_version_of_the_protocol">ནོར་འཁྲུལ།: ཁྱེད་ལ་པར་གཞི་གྲོས་མཐུན་གྱིས་རྒྱབ་སྐྱོར་མ་བྱས་བའི་སྤྱི་སྤྱོད་ལྡེ་མིག་ཞིག་འབྱོར་གི་རེད། ལྡེ་མིག་དེས་ལས་ཀ་བྱེད་ཐུབ་གི་མ་རེད། སྐུ་མཁྱེན། ཉེན་སྲུང་གི་སྡེ་ཚན་ཞིག་བསྐྱར་དུ་འགོ་ཚུགས། </string>
<string name="ReceiveKeyActivity_this_key_exchange_message_does_not_include_an_identity_signature">ལྡེ་མིག་བརྗེ་ལེན་བྱེད་རྒྱུའི་བརྡ་ལན་འདིའི་ནང་ངོས་འཛིན་མིང་རྟགས་མེད།</string>
<string name="ReceiveKeyActivity_this_key_exchange_message_includes_an_identity_signature_but_you_do_not_yet_trust_it">ལྡེ་མིག་བརྗེ་ལེན་བྱེད་རྒྱུའི་བརྡ་ལན་འདིའི་ནང་ངོས་འཛིན་མིང་རྟགས་ཡོད། ཡིན་ནའང་ད་ལྟ་ཁྱོད་ཀྱི་དེར་བློ་འགེལ་མེད།</string>
<string name="ReceiveKeyActivity_this_key_exchange_message_includes_an_identity_signature_which_you_trust_for_s">ལྡེ་མིག་བརྗེ་ལེན་བྱེད་རྒྱུའི་བརྡ་ལན་འདིའི་ནང་ངོས་འཛིན་མིང་རྟགས་ཡོད་པ་དེ་རྒྱུ་མཚན་གང་གི་དོན་དུ་བློ་འགེལ་ཡོད། : %s</string>
<string name="ReceiveKeyActivity_this_is_the_key_that_you_sent_to_start_your_current_encrypted_session_with_s">%s དང་ལྷན་དུ་འགོ་འཛུགས་པའི་བདེ་འཇགས་ཀྱི་གཏམ་གླེང་བྱེད་བཞིན་པ་དེའི་ལྡེ་མིག་འདི་རེད།</string>
<string name="ReceiveKeyActivity_this_is_the_key_that_you_received_to_start_your_current_encrypted_session_with_s">%s དང་ལྷན་དུ་འགོ་འཛུགས་པའི་བདེ་འཇགས་ཀྱི་གཏམ་གླེང་བྱེད་བཞིན་པ་དེའི་ལྡེ་མིག་འབྱོར་བ་འདི་རེད།</string>
<string name="ReceiveKeyActivity_you_have_received_a_key_exchange_message_from_s_warning_you_already_have_an_encrypted_session">ཁྱེད་ལ་(%s.⏎ ⏎ )ནས་ཕན་ཚུན་བརྗེ་ལེན་གྱི་གསང་བའི་འཕྲིན་ཐུང་ཞིག་འབྱོར་འདུག \nཉེན་བརྡ།: འབྲེལ་བ་འདི་དང་མཉམ་དུ་ཁྱོད་ལ་གསང་རྒྱ་ཅན་གྱི་སྡེ་ཚན་ཞིག་འདུག གལ་སྲིད་ཁྱོད་ནས་ཕན་ཚུན་བརྗེ་ལེན་གྱི་གསང་བའི་འཕྲིན་ཐུང་འདི་ངོས་ལེན་བྱས་ཚེ། དེས་ཁྱེད་ཀྱི་ད་ཡོད་སྡེ་ཚན་དེ་གཏོར་བཤིག་གཏོང་བ་མ་ཟད་ཁྱེད་ནས་ཡང་བསྐྱར་ཁུངས་སྐྱེལ་བྱེད་དགོས། ཁྱེད་ནས་གསང་བའི་བརྗེ་ལེན་འདི་ལེགས་འགྲུབ་བྱེད་འདོད་དམ། </string>
<string name="ReceiveKeyActivity_you_have_received_a_key_exchange_message_from_s_you_have_previously_initiated">%s ནས་ལྡེ་མིག་བརྗེ་ལེན་བྱེད་རྒྱུའི་བརྡ་ལན་འབྱོར་འདུག ཁྱེད་ཀྱི་འབྲེལ་མིང་འདིའི་ལྷན་དུ་གཏམ་གླེང་འགོ་འཛུགས་འདུག ལྡེ་མིག་འདི་ལེན་མ་ཐག་ཏུ་ལྡེ་མིག་བརྗེ་ལེན་གྱི་རིམ་པ་ཆ་ཚང་བ་ཡིན། ཁྱེད་རང་ནས་ལྡེ་མིག་འདིའི་བརྗེ་བསྒྱུར་ལེགས་འགྲུབ་བྱེད་འདོད་འདུག་གམ།</string>
<string name="ReceiveKeyActivity_you_have_initiated_a_key_exchange_message_with_s_but_have_not_yet_received_a_reply">ཁྱོད་ཀྱི་ %d ལྡེ་མིག་བརྗེ་ལེན་བྱ་རྒྱུའི་བརྡ་ལེན་བཏང་ཟིན་འདུག ཡིན་ནའང་དེར་ད་ལྟ་ཡ་ལན་མིན་འདུག</string>
<string name="ReceiveKeyActivity_you_have_received_a_key_exchange_message_from_s_you_have_no_existing_session">%s ནས་ལྡེ་མིག་བརྗེ་ལེན་བྱེད་རྒྱུའི་བརྡ་ལན་འབྱོར་འདུག You have no existing session with this contact, ཁྱེད་རང་ནས་ལྡེ་མིག་འདིའི་བརྗེ་བསྒྱུར་ལེགས་འགྲུབ་བྱེད་འདོད་འདུག་གམ།</string>
<!--ReviewIdentitiesActivity-->
<string name="ReviewIdentitiesActivity_unable_to_view_corrupted_identity_key_exclamation">ངན་ལྷད་ཅན་གྱི་ངོས་འཛིན་ལྡེ་མིག་དེར་བལྟ་མི་ཐུབ།</string>
<string name="ReviewIdentitiesActivity_delete_identity">ངོས་འཛིན་ལྡེ་མིག་གསུབ་རྒྱུ་ཡིན་ནམ།</string>
<string name="ReviewIdentitiesActivity_delete_identity_are_you_sure_you_want_to_delete_this_identity_key">ཁྱེད་ཀྱིས་དངོས་གནས་ངོས་འཛིན་ལྡེ་མིག་དེ་གཏན་དུ་གསུབ་རྒྱུ་ཡིན་ནམ།</string>
<string name="ReviewIdentitiesActivity_invalid_identity">ངོ་སྤྲོད་ཁུངས་ལྡན་མིན།</string>
<!--SaveIdentityActivity-->
<string name="SaveIdentityActivity_you_must_specify_a_name_for_this_identity_exclamation">ངོ་རྟགས་འདི་ལ་མིང་དམིགས་བསལ་བ་ཞིག་ཐོགས། </string>
<string name="SaveIdentityActivity_identity_name_exists_exclamation">ངོ་རྟགས་གི་མིང་འདུག </string>
<string name="SaveIdentityActivity_an_identity_key_with_the_specified_name_already_exists">མིང་ཁ་གསལ་སྟོན་པའི་ངོས་འཛིན་ལྡེ་མིག་ཅིག་སྔོན་ནས་འདུག</string>
<string name="SaveIdentityActivity_manage_identities">ངོ་རྟགས་རྣམས་བཟོ་སྒྲིག་བྱོས། </string>
<!--VerifyIdentityActivity-->
<string name="VerifyIdentityActivity_mark_identity_verified_question">ངོ་རྟགས་བདེན་དབང་གི་རྟགས་བཀོད་དམ། </string>
<string name="VerifyIdentityActivity_are_you_sure_you_have_validated_the_recipients_identity_fingerprint_and_would_like_to_mark_it_as_verified">ཁྱེད་ལ་ཁུངས་ཐུབ་ཀྱི་ངོ་རྟགས་དེའི་མཛུབ་ཐེལ་ཡོད་པ་མ་ཟད་དེ་ལ་ཁུངས་ཐུབ་ཀྱི་རྟགས་རྒྱག་ཐུབ་བམ། </string>
<string name="VerifyIdentityActivity_mark_verified">ཁུངས་ཐུབ་ཀྱི་རྟགས་བཀོད། </string>
<string name="VerifyIdentityActivity_you_do_not_have_an_identity_key">ཁྱེད་ལ་ངོས་འཛིན་བྱེད་ཀྱི་ལྡེ་མིག་མི་འདུག</string>
<string name="VerifyIdentityActivity_recipient_has_no_identity_key">སྦྱོར་ཡུལ་ལ་ངོས་འཛིན་བྱེད་ཀྱི་ལྡེ་མིག་མི་འདུག</string>
<string name="VerifyIdentityActivity_recipient_has_no_identity_key_exclamation">སྦྱོར་ཡུལ་ལ་ངོས་འཛིན་བྱེད་ཀྱི་ལྡེ་མིག་མི་འདུག</string>
<string name="VerifyIdentityActivity_scan_their_key_to_compare">གཞན་གྱི་ལྡེ་མིག་འགྲན་ཆེད་ཞིབ་བརྟག་བྱེད།</string>
<string name="VerifyIdentityActivity_get_my_key_scanned">ངའི་ལྡེ་མིག་ཞིབ་བརྟག་བྱོས།</string>
<string name="VerifyIdentityActivity_warning_the_scanned_key_does_not_match_please_check_the_fingerprint_text_carefully">ཉེན་བརྡ། ཞིབ་བརྟག་བྱས་པའི་ལྡེ་མིག་དེ་མི་མཚུངས། མཛུབ་རིས་ཡི་གེ་དེ་གཟབ་ནན་ངང་ཞིབ་བཤེར་བྱོས།</string>
<string name="VerifyIdentityActivity_not_verified_exclamation">ར་སྤྲོད་བྱས་མི་འདུག</string>
<string name="VerifyIdentityActivity_their_key_is_correct_it_is_also_necessary_to_verify_your_key_with_them_as_well">གཞན་གྱི་ལྡེ་མིག་ཏག་ཏག་རེད། ཁྱེད་ཀྱི་ལྡེ་མིག་གཞན་གྱིས་ར་སྤྲོད་བྱེད་དགོས་ངེས།</string>
<string name="VerifyIdentityActivity_verified_exclamation">ར་སྤྲོད་བྱས་ཟིན།</string>
<string name="VerifyIdentityActivity_you_don_t_have_an_identity_key_exclamation">ཁྱེད་ལ་ངོས་འཛིན་བྱེད་ཀྱི་ལྡེ་མིག་མི་འདུག</string>
<!--VerifyImportedIdentityActivity-->
<string name="VerifyImportedIdentityActivity_you_must_specify_a_name_for_this_contact_exclamation">འབྲེལ་བ་བྱ་ཡུལ་དེའི་མིང་ངེས་པར་དུ་ཁ་གསལ་སྟོན་དགོས།</string>
<string name="VerifyImportedIdentityActivity_save_identity_key_question">ངོས་འཛིན་ལྡེ་མིག་དེ་ཉར་ཚགས་བྱེད་རྒྱུ་ཡིན་ནམ།</string>
<string name="VerifyImportedIdentityActivity_error_saving_identity_key_exclamation">ངོས་འཛིན་ལྡེ་མིག་ཉར་ཚགས་བྱེད་སྐབས་ནོར་འཁྲུལ་བྱུང་།</string>
<string name="VerifyImportedIdentityActivity_this_identity_key_or_an_identity_key_with_the_same_name_already_exists_please_edit_your_key_database">ངོས་འཛིན་ལྡེ་མིག་འདི་འམ། ཡང་ན་མིང་མཚུངས་ཅན་གྱི་ལྡེ་མིག་ཞིག་སྔོན་ནས་འདུག ལྡེ་མིག་གི་གཞི་གྲངས་དེ་རྩོམ་སྒྲིག་བྱེད།</string>
<string name="VerifyImportedIdentityActivity_scan_to_compare">འགྲན་ཆེད་ཞིབ་བརྟག་བྱེད།</string>
<string name="VerifyImportedIdentityActivity_get_scanned_to_compare">ལྡེ་མིག་འགྲན་ཆེད་ཞིབ་བརྟག་བྱེད།</string>
<string name="VerifyImportedIdentityActivity_not_verified_exclamation">ར་སྤྲོད་བྱས་མི་འདུག</string>
<string name="VerifyImportedIdentityActivity_warning_the_scanned_key_does_not_match_exclamation">ཉེན་བརྡ། ཞིབ་བརྟག་བྱས་པའི་ལྡེ་མིག་དེ་མི་མཚུངས།</string>
<string name="VerifyImportedIdentityActivity_the_scanned_key_matches_exclamation">ཞིབ་བརྟག་བྱས་པའི་ལྡེ་མིག་དེ་མཚུངས།</string>
<string name="VerifyImportedIdentityActivity_verified_exclamation">ར་སྤྲོད་བྱས་ཟིན།</string>
<string name="VerifyImportedIdentityActivity_are_you_sure_that_you_would_like_to_mark_this_as_a_valid_identity_key_for_all_future_correspondence_with_s">ཁྱེད་ཀྱིས་འབྱུང་འགྱུར་གྱི་ཡིག་འབྲེལ་ཚང་མའི་མཉམ་དུ། ཁུངས་ཐུབ་ཀྱི་ངོ་རྟགས་འདིའི་ལྡེ་མིག་ལ་རྟགས་རྒྱག་འདོད་ཡོད་དམ། སྤྱིར་བཏང་ནས་ཁྱེད་ཀྱིས་མཛུབ་ཐེལ་ཁུངས་སྐྱེལ་བྱས་ཡོད་ན། དེ་གཟོད་འདི་འདི་ལྟར་བྱེད་དགོས་རེད། </string>
<!--VerifyKeysActivity-->
<string name="VerifyKeysActivity_mark_session_verified_question">ཁུངས་ཐུབ་ཀྱི་སྡེ་ཚན་ལ་རྟགས་བཀོད། </string>
<string name="VerifyKeysActivity_are_you_sure_that_you_have_validated_these_fingerprints_and_would_like_to_mark_this_session_as_verified">ཁྱེད་ཀྱིས་མཛུབ་ཐེལ་འདི་ཚོ་ཁུངས་སྐྱེལ་བྱས་ཡོད་པ་མ་ཟད་སྡེ་ཚན་འདི་ལ་བདེན་དཔང་ཡིན་པའི་རྟགས་རྒྱག་གི་ཡིན་ནམ། </string>
<string name="VerifyKeysActivity_mark_verified">རྒྱབ་གཉེར་གྱི་རྟགས་བཀོད། </string>
<string name="VerifyKeysActivity_get_my_fingerprint_scanned">ངའི་མཛུབ་རིས་ཞིབ་བརྟག་བྱེད།</string>
<string name="VerifyKeysActivity_scan_their_fingerprint">གཞན་གྱི་མཛུབ་རིས་ཞིབ་བརྟག་བྱེད།</string>
<string name="VerifyKeysActivity_warning_the_scanned_key_does_not_match_please_check_the_fingerprint_text_carefully2">ཉེན་བརྡ། ཞིབ་བརྟག་བྱས་པའི་ལྡེ་མིག་དེ་མི་མཚུངས། མཛུབ་རིས་ཡི་གེ་དེ་གཟབ་ནན་ངང་ཞིབ་བཤེར་བྱོས།</string>
<string name="VerifyKeysActivity_not_verified_exclamation">ར་སྤྲོད་བྱས་མི་འདུག</string>
<string name="VerifyKeysActivity_their_key_is_correct_it_is_also_necessary_to_get_your_fingerprint_scanned_as_well">གཞན་གྱི་ལྡེ་མིག་ཏག་ཏག་རེད། ཁྱེད་ཀྱི་མཛུབ་རིས་ཞིབ་བརྟག་བྱེད་དགོས་ངེས།</string>
<string name="VerifyKeysActivity_verified_exclamation">ར་སྤྲོད་བྱས་ཟིན།</string>
<!--ViewIdentityActivity-->
<string name="ViewIdentityActivity_you_do_not_have_an_identity_key">ཁྱེད་ལ་ངོས་འཛིན་བྱེད་ཀྱི་ལྡེ་མིག་མི་འདུག</string>
<string name="ViewIdentityActivity_scan_to_compare">འགྲན་ཆེད་ཞིབ་བརྟག་བྱེད།</string>
<string name="ViewIdentityActivity_get_scanned_to_compare">ལྡེ་མིག་འགྲན་ཆེད་ཞིབ་བརྟག་བྱེད།</string>
<string name="ViewIdentityActivity_warning_the_scanned_key_does_not_match_exclamation">ཉེན་བརྡ། ཞིབ་བརྟག་བྱས་པའི་ལྡེ་མིག་དེ་མི་མཚུངས།</string>
<string name="ViewIdentityActivity_not_verified_exclamation">ར་སྤྲོད་བྱས་མི་འདུག</string>
<string name="ViewIdentityActivity_the_scanned_key_matches_exclamation">ཞིབ་བརྟག་བྱས་པའི་ལྡེ་མིག་དེ་མཚུངས།</string>
<string name="ViewIdentityActivity_verified_exclamation">ར་སྤྲོད་བྱས་ཟིན།</string>
<!--KeyExchangeInitiator-->
<string name="KeyExchangeInitiator_initiate_despite_existing_request_question">ད་ཡོད་ཀྱི་རེ་སྐུལ་ལ་མ་ལྟོས་པར་འགོ་རྩོམ། </string>
<string name="KeyExchangeInitiator_youve_already_sent_a_session_initiation_request_to_this_recipient_are_you_sure">ཁྱེད་ཀྱིས་གཏོང་ས་དེར་རེ་སྐུལ་གྱི་སྡེ་ཚན་ཞིག་བཏང་ཚར་འདུག ཁྱེད་ཀྱིས་ཡང་བསྐྱར་གཅིག་གཏོང་འདོད་འདུག་གམ འདི་ཡིས་ཁྱེད་ཀྱི་རེ་སྐུལ་དང་པོ་དེ་རྩིས་མེད་གཏོང་གི་རེད། </string>
<string name="KeyExchangeInitiator_send">གཏོང་།</string>
<!--MessageDisplayHelper-->
<string name="MessageDisplayHelper_bad_encrypted_message">གསང་སྡོམ་འཕྲིན་ཐུང་ཕན་མེད་ཞིག </string>
<string name="MessageDisplayHelper_decrypting_please_wait">གསང་སྡོམ་བཤིག་བཞིན་ཡོད། ཅུང་སྒུག་དང་།</string>
<string name="MessageDisplayHelper_message_encrypted_for_non_existing_session">རྙེད་རྒྱུ་མེད་པའི་སྡེ་ཚན་ཞིག་གི་ཆེད་དུ་འཕྲིན་ཐུང་གསང་སྡོམ་བྱས་ཡོད། </string>
<string name="MessageDisplayHelper_decryption_error_local_message_corrupted_mac_doesn_t_match_potential_tampering_question">གསང་གྲོལ་ནོར་སྐྱོན། </string>
<!--MmsDatabase-->
<string name="MmsDatabase_connecting_to_mms_server">MMS ཞབས་ཞུའི་ཡོ་ཆས་ལ་མཐུད་བཞིན་པ། </string>
<string name="MmsDatabase_downloading_mms">MMS ཕབ་ལེན་བྱེད་བཞིན་ཡོད།</string>
<string name="MmsDatabase_mms_download_failed">MMS ཕབ་ལེན་བྱེད་མི་ཐུབ།</string>
<string name="MmsDatabase_downloading">ཕབ་ལེན་བྱེད་བཞིན་པ། </string>
<string name="MmsDatabase_anonymous">མིང་མེད། </string>
<!--MmsMessageRecord-->
<string name="MmsMessageRecord_decrypting_mms_please_wait">MMS གསང་གྲོལ་བྱེད་བཞིན་ཡོད། ཅུང་སྒུག་རོགས། </string>
<string name="MmsMessageRecord_bad_encrypted_mms_message">མཁོ་མེད་གསང་སྡོམ་ཅན་གྱི་MMSའཕྲིན་ཐུང་། </string>
<!--ApplicationMigrationService-->
<string name="ApplicationMigrationService_migrating">གནས་སྤོས།</string>
<!--KeyCachingService-->
<string name="KeyCachingService_textsecure_passphrase_cached">ཡི་གེའི་ཉེན་སྲུང་གི་གསང་ཚིག་དེ་སྦས།</string>
<string name="KeyCachingService_passphrase_cached">གསང་ཚིག་དེ་སྦས།</string>
<!--MessageNotifier-->
<string name="MessageNotifier_d_new_messages">(%d) འཕྲིན་ཐུང་གསར་པ་རྣམས།</string>
<string name="MessageNotifier_most_recent_from_s">%: ནས་ཉེ་ཆར་གྱི་དེ། </string>
<!--auto_initiate_activity-->
<string name="auto_initiate_activity__you_have_received_a_message_from_someone_who_supports_textsecure_encrypted_sessions_would_you_like_to_initiate_a_secure_session">འཕྲིན་ཐུང་བདེ་འཇགས་ཀྱི་བདེ་འཇགས་གཏམ་གླེང་ལ་རྒྱབ་སྐྱོར་བྱེད་མཁན་ཞིག་ནས་ཁྱེད་ལ་འཕྲིན་ཐུང་ཅིག་འབྱོར་འདུག ཁོང་གི་ལྷན་དུ་འབྲེལ་བ་བཙན་པོ་ཡོང་ཆེད་ལྡེ་མིག་བརྗེ་ལེན་རྒྱུ་ཡིན་ནམ།</string>
<string name="auto_initiate_activity__initiate_exchange">བརྗེ་ལེན་འགོ་བཙུགས།</string>
<!--change_passphrase_activity-->
<string name="change_passphrase_activity__old_passphrase">གསང་ཚིག་རྙིང་པ།</string>
<string name="change_passphrase_activity__new_passphrase">གསང་ཡིག་གསར་པ།</string>
<string name="change_passphrase_activity__repeat_new_passphrase">གསང་ཡིག་གསར་པ་བསྐྱར་དུ་བྲིས།</string>
<!--contact_selection_group_activity-->
<!--contact_selection_list_activity-->
<string name="contact_selection_group_activity__no_contacts">འབྲེལ་མིང་མི་འདུག</string>
<!--ContactSelectionListFragment-->
<string name="ContactSelectionlistFragment_select_for">ཆེད་དུ་འདེམས།</string>
<!--contact_selection_recent_activity-->
<string name="contact_selection_recent_activity__no_recent_calls">ཉེ་ཆར་ཁ་པར་གཏོང་བ་མི་འདུག</string>
<!--conversation_activity-->
<string name="conversation_activity__type_message">འཕྲིན་ཐུང་བྲིས།</string>
<string name="conversation_activity__send">གཏོང་།</string>
<string name="conversation_activity__remove">ཕྱིར་ཕུད། </string>
<!--conversation_fragment_cab-->
<!--create_passphrase_activity-->
<string name="create_passphrase_activity__please_choose_a_passphrase_that_will_be_used_to_locally_encrypt_your_data_this_should_be_a_strong_passphrase">རྒྱུན་ལྡན་ནས་ཁྱེད་ཀྱིས་གསང་སྡོམ་རེའུ་མིག་ལ་བེད་སྤྱོད་པའི་གསང་ཚིག་དེ་དང་། དེ་གསང་ཚིག་བརྟན་པོ་ཆགས་གི་རེད། </string>
<string name="create_passphrase_activity__passphrase">གསང་ཡིག :</string>
<string name="create_passphrase_activity__repeat">བསྐྱར་དུ། :</string>
<!--receive_key_activity-->
<string name="receive_key_activity__session">གཏམ་གླེང་།</string>
<string name="receive_key_activity__identities">ངོ་རྟགས། </string>
<string name="receive_key_activity__complete_exchange">བརྗེ་ལེན་ལེགས་འགྲུབ་བྱོས། </string>
<!--recipients_panel-->
<string name="recipients_panel__to">ལ། </string>
<!--review_identities-->
<string name="review_identities__you_don_t_currently_have_any_identity_keys_in_your_trust_database">གནས་སྐབས་རིང་། ཁྱེད་ཀྱི་གཞི་གྲངས་མཛོད་ཀྱི་ཁོངས་སུ་ཁྱེད་ལ་ངོ་རྟགས་ལྡེ་མིག་མི་འདུག </string>
<!--save_identity_activity-->
<string name="save_identity_activity__identity_name">ངོ་རྟགས་ཀྱི་མིང་། </string>
<!--verify_identity_activity-->
<string name="verify_identity_activity__their_identity_they_read">གཞན་གྱི་ངོས་འཛིན།:\\n(གཞན་གྱི་ཀློག)</string>
<string name="verify_identity_activity__your_identity_you_read">ཁྱེད་ཀྱི་ངོས་འཛིན།:\\n༼ཁྱེད་ཀྱི་ཀློག༽</string>
<!--verify_import_identity_activity-->
<string name="verify_import_identity_activity__identity_name_n">ངོས་འཛིན་གྱི་མིང་།</string>
<string name="verify_import_identity_activity__imported_identity_n">ནང་འཇུག་བྱས་པའི་ངོས་འཛིན།:</string>
<string name="verify_import_identity_activity__verified">ར་སྤྲོད་བྱས་ཟིན།</string>
<string name="verify_import_identity_activity__compare">བསྡུར། </string>
<!--verify_keys_activity-->
<string name="verify_keys_activity__they_read_this">གཞན་གྱི་འདི་ཀློགས།</string>
<string name="verify_keys_activity__you_read_this">ཁྱེད་ཀྱི་འདི་ཀློགས:</string>
<!--view_identity_activity-->
<string name="view_identity_activity__identity">ངོ་རྟགས། </string>
<!--AndroidManifest.xml-->
<string name="AndroidManifest__create_passphrase">གསང་ཚིག་གསར་བཟོ་བྱོས། </string>
<string name="AndroidManifest__enter_passphrase">གསང་ཡིག་འཇོག</string>
<string name="AndroidManifest__select_contacts">འབྲེལ་བ་བྱེད་ས་འདེམས། </string>
<string name="AndroidManifest__public_identity_key">སྤྱི་བའི་ངོ་རྟགས་ལྡེ་མིག </string>
<string name="AndroidManifest__change_passphrase">གསང་ཚིག་བརྗེ་བོ་རྒྱོབས།</string>
<string name="AndroidManifest__verify_session">གཏམ་གླེང་སྤྲོད་བྱོས།</string>
<string name="AndroidManifest__verify_identity">ངོ་རྟགས་ར་སྤྲོད་བྱོས། </string>
<string name="AndroidManifest__save_identity">ངོ་རྟགས་སྲུང་། </string>
<string name="AndroidManifest__manage_identity_keys">ངོ་རྟགས་ཀྱི་ལྡེ་མིག་ལ་དོ་དམ་བྱོས། </string>
<string name="AndroidManifest__complete_key_exchange">ལྡེ་མིག་བརྗེ་ལེན་ལེགས་འགྲུབ་བྱོསས྄་</string>
<string name="AndroidManifest__verify_imported_identity">ནང་འདྲེན་ངོ་རྟགས་ར་སྤྲོད་བྱོས། </string>
<!--preferences.xml-->
<string name="preferences__use_settings">སྒྲིག་བཟོ་བཀོལ།</string>
<string name="preferences__pref_all_sms_title">SMS ཚང་མའི་ཆེད་དུ་བེད་སྤྱོད་ཐོངས།</string>
<string name="preferences__pref_all_mms_title">MMS ཆ་ཚང་གི་ཆེད་དུ་བེད་སྤྱོད་ཐོངས།</string>
<string name="preferences__use_textsecure_for_viewing_and_storing_all_incoming_text_messages">ནང་ཡོང་འཕྲིན་ཐུང་ཚང་མར་བལྟ་བ་དང་ཉར་བ་ལ། ཡི་གེའི་ཉེན་སྲུང་བེད་སྤྱོད་ཐོངས།</string>
<string name="preferences__use_textsecure_for_viewing_and_storing_all_incoming_multimedia_messages">སྣ་མང་འཕྲིན་ཐུང་རིགས་ལ་ཉར་ཚགས་དང་ཞིབ་ལྟའི་ཆེད་དུ། ཡི་གེའི་ཉེན་སྲུང་བེད་སྤྱོད་ཐོངས།</string>
<string name="preferences__input_settings">སྒྲིག་བཟོ་ནང་དུ་འཇུག</string>
<string name="preferences__pref_enter_sends_title">གཏོང་བའི་སྒང་ལ་བསྣུན། </string>
<string name="preferences__pressing_the_enter_key_will_send_text_messages">ནང་འཛུལ་ལྡེ་མིག་ལ་བསྣུན་ནས་འཕྲིན་ཐུང་གཏོང་།</string>
<string name="preferences__display_settings">སྒྲིག་བཀོད་བྱེད་ས་སྟོན།</string>
<string name="preferences__choose_identity">ངོ་རྟགས་འདེམས། </string>
<string name="preferences__choose_your_contact_entry_from_the_contacts_list">འབྲེལ་ཐོ་ནས་ཁྱེད་ཀྱིས་འབྲེལ་བ་བྱེད་ས་དེ་གདམ།</string>
<string name="preferences__encryption_settings">གསང་སྡོམ་སྒྲིག་བཟོ།</string>
<string name="preferences__change_passphrase">གསང་ཚིག་བརྗེ་བོ་རྒྱོབས།</string>
<string name="preferences__change_my_passphrase">ངའི་གསང་ཚིག་བརྗེ་བོ་རྒྱོབས།</string>
<string name="preferences__complete_key_exchanges">ལྡེ་མིག་བརྗེ་ལེན་ཚར་བ་བྱོས།</string>
<string name="preferences__automatically_complete_key_exchanges_for_new_sessions_or_for_existing_sessions_with_the_same_identity_key">ལྡེ་མིག་གི་ངོ་རྟགས་གཅིག་པའི་ད་ཡོད་སྡེ་ཚན་ཞིག་གམ་ཡང་ན་སྡེ་ཚན་གསར་བ་ཞིག་གི་སླད་དུ། རང་བཞིན་ནས་ལྡེ་མིག་བརྗེ་ལེན་དེ་འགྲུབ་པ་བྱོས།</string>
<string name="preferences__include_a_whitespace_tag_at_the_end_of_every_non_encrypted_message">གསང་སྡོམ་མ་ཡིན་པའི་འཕྲིན་ཐུང་རེ་རེའི་རྗེས་སུ། དཀར་ངོས་ཀྱི་འཛེར་མ་དེ་སྦྱར།</string>
<string name="preferences__include_whitespace_tag">དཀར་ངོས་ཀྱི་འཛེར་མ་དེ་མཉམ་དུ་སྦྱར།</string>
<string name="preferences__sign_key_exchange_messages_with_identity_key">ཡོང་མཐུན་གྱི་ལྡེ་མིག་དེའི་མཉམ་དུ། འཕྲིན་ཐུང་བརྗེ་བསྒྱུར་གྱི་ལྡེ་མིག་དེར་སྲིབ་རྟགས་རྒྱོབས།</string>
<string name="preferences__sign_key_exchange">ལྡེ་མིག་བརྗེ་བསྒྱུར་དེར་སྲིབ་རྟགས་རྒྱོབས།</string>
<string name="preferences__forget_passphrase_from_memory_after_some_interval">དུས་བརྒལ་གྱི་དབར་ནས། དྲན་ཐོ་ནས་གསང་ཚིག་བརྗེད།</string>
<string name="preferences__timeout_passphrase">གསང་ཚིག་གི་དུས་ཚོད་ཡོལ་འདུག</string>
<string name="preferences__pref_timeout_interval_title">བར་སེང་མཚམས་འཇོག</string>
<string name="preferences__the_amount_of_time_to_wait_before_forgetting_passphrase">དྲན་ཐོ་ནས་གསང་ཚིག་མ་བརྗེད་གོང་སྒུག་ཡུན་དུས་ཚོད།</string>
<string name="preferences__identity_key_settings">ངོ་རྟགས་ཀྱི་ལྡེ་མིག་སྒྲིག་བཟོ། </string>
<string name="preferences__view_my_identity_key">ངའི་ངོས་འཛིན་ལྡེ་མིག་ལྡེ་མིག་ལ་བལྟ།</string>
<string name="preferences__export_my_identity_key">ངའི་ངོས་འཛིན་བྱེད་ཀྱི་ལྡེ་མིག་དེ་ཕྱིར་འདོན་བྱེད།</string>
<string name="preferences__import_contacts_key">འབྲེལ་བ་བྱེད་སའི་ལྡེ་མིག་ནང་འདྲེན་བྱོས། </string>
<string name="preferences__import_an_identity_key_from_a_contact">འབྲེལ་བ་བྱེད་ས་ནས་ངོ་རྟགས་ལྡེ་མིག་ནང་འདྲེན་བྱོས། </string>
<string name="preferences__manage_identity_keys">ངོ་རྟགས་ཀྱི་ལྡེ་མིག་དོ་དམ་བྱོས། </string>
<string name="preferences__manage_configured_identity_keys">སྡེབ་སྒྲིག་ཟིན་པའི་ངོ་རྟགས་ལྡེ་མིག་དོ་དམ་བྱོས། </string>
<string name="preferences__notification_settings">བརྡ་གཏོང་སྒྲིག་བཟོ།</string>
<string name="preferences__notifications">བརྡ་གཏོང་།</string>
<string name="preferences__led_color">LED ཡི་ཚོན།</string>
<string name="preferences__change_notification_led_color">LED ཡི་ཚོན་གྱི་བརྡ་གཏོང་བརྗེས།</string>
<string name="preferences__pref_led_blink_title">LED ཡི་རྒྱན་རིས།</string>
<string name="preferences__change_notification_blink_pattern">LED རྒྱན་རིས་ཀྱི་བརྡ་གཏོང་བརྗེས།</string>
<string name="preferences__select_ringtone">དྲིལ་སྒྲ་གདམ།</string>
<string name="preferences__vibrate"> འདར་བཅུག</string>
<string name="preferences__also_vibrate_when_notified">བརྡ་གཏོང་སྐབས་ཡང་འདར་བཅུག</string>
<string name="preferences__minutes">སྐར་མ།</string>
<string name="preferences__hours">ཆུ་ཚོད།</string>
<string name="preferences__green">ལྗང་ཁུ།</string>
<string name="preferences__red">དམར་པོ།</string>
<string name="preferences__blue">སྔོན་པོ།</string>
<string name="preferences__orange">ལི་ཁྲི།</string>
<string name="preferences__cyan">སྔོ་དཀར།</string>
<string name="preferences__magenta">རྒྱ་ཚོས་ཀྱི་ཚོས་གཞི།</string>
<string name="preferences__fast">མགྱོགས་པོ།</string>
<string name="preferences__normal">འཆར་ཅན།</string>
<string name="preferences__slow">དལ་བུ།</string>
<string name="preferences__custom">རང་གྲུབ།</string>
<!--****************************************-->
<!--menus-->
<!--****************************************-->
<!--contact_selection_list-->
<string name="contact_selection_list__menu_select_all">ཚང་མ་འདེམས།</string>
<string name="contact_selection_list__menu_unselect_all">འདེམས་པ་ཡོངས་རྫོགས་མེད་པར་ཟོས།</string>
<!--contact_selection-->
<string name="contact_selection__menu_finished">མཇུག་རྫོགས་སོ།</string>
<!--conversation_button_context-->
<string name="conversation_button_context__menu_send_unencrypted">གསང་སྡོམ་མ་བྱེད་པའི་འཕྲིན་ཐུང་བསྐུར།</string>
<!--conversation_callable-->
<string name="conversation_callable__menu_call">ཁ་པར་གཏོང་།</string>
<!--conversation_context-->
<string name="conversation_context__menu_message_details">འཕྲིན་ཐུང་ཞིབ་ཆ།</string>
<string name="conversation_context__menu_copy_text">འཕྲིན་ཐུང་འདྲ་བཟོ་བྱེད།</string>
<string name="conversation_context__menu_delete_message">འཕྲིན་ཐུང་སུབ།</string>
<string name="conversation_context__menu_forward_message">འཕྲིན་ཐུང་བརྒྱུད་འགྲེམ་བྱེད།</string>
<!--conversation_insecure-->
<string name="conversation_insecure__menu_start_secure_session">བདེ་འཇགས་གཏམ་གླེང་འགོ་བརྩམ།</string>
<!--conversation_list_batch-->
<string name="conversation_list_batch__menu_delete_selected">འདེམས་པ་རྣམས་སུབ།</string>
<string name="conversation_list_batch__menu_select_all">ཚང་མ་འདེམས།</string>
<!--conversation_list-->
<string name="conversation_list__menu_search">འཚོལ།</string>
<!--conversation_secure_verified-->
<!--conversation_secure_unverified-->
<string name="conversation_secure_verified__menu_security">ཉེན་སྲུང་།</string>
<string name="conversation_secure_verified__menu_verify_session">གཏམ་གླེང་ར་སྤྲོད་བྱེད།</string>
<string name="conversation_secure_verified__menu_verify_recipient">འཕྲིན་ཐུང་འབྱོར་ཡུལ་དེ་ར་སྤྲོད་བྱོས།</string>
<string name="conversation_secure_verified__menu_abort_secure_session">བདེ་འཇགས་གཏམ་གླེང་མཚམས་འཇོག</string>
<!--conversation-->
<string name="conversation__menu_add_attachment">ཟུར་སྣོན་འཇོག</string>
<string name="conversation__menu_delete_thread">འཕྲིན་ཐུང་སྐུད་རིམ་སུབ།</string>
<string name="conversation__menu_compare">བསྡུར། </string>
<!--conversation_group_options-->
<!--key_scanning-->
<string name="key_scanning__menu_compare">བསྡུར། </string>
<string name="key_scanning__menu_get_scanned_to_compare">ལྡེ་མིག་འགྲན་ཆེད་ཞིབ་བརྟག་བྱོས།</string>
<string name="key_scanning__menu_scan_to_compare">ལྡེ་མིག་འགྲན་ཆེད་ཞིབ་བརྟག་བྱོས།</string>
<!--text_secure_locked-->
<string name="text_secure_locked__menu_unlock">སྒོ་ཕྱེས། </string>
<!--text_secure_normal-->
<string name="text_secure_normal__menu_new_message">འཕྲིན་ཐུང་གསར་པ།</string>
<string name="text_secure_normal__menu_settings">སྒྲིག་བཟོ།</string>
<string name="text_secure_normal__menu_import_export">ནང་འཇུག/ཕྱིར་འདོན།</string>
<string name="text_secure_normal__menu_import">ནང་འཇུག་བྱོས།</string>
<string name="text_secure_normal__menu_export">ཕྱིར་འདོན་བྱེད།</string>
<string name="text_secure_normal__menu_clear_passphrase">གསང་ཡིག་མེད་པ་བཟོས།</string>
<!--verify_keys-->
<string name="verify_keys__menu_verified">ར་སྤྲོད་བྱས་ཟིན།</string>
<!--Misc. piggybacking-->
<!--EOF-->
</resources>

View File

@@ -35,6 +35,7 @@
<string name="ApplicationPreferenceActivity_you_need_to_have_entered_your_passphrase_before_importing_keys">Bevor Sie Schlüssel importieren können, müssen Sie ihr Passwort eingeben...</string>
<string name="ApplicationPreferenceActivity_you_need_to_have_entered_your_passphrase_before_managing_keys">Bevor Sie Schlüssel verwalten können, müssen Sie ihr Passwort eingeben...</string>
<string name="ApplicationPreferenceActivity_you_havent_set_a_passphrase_yet">Sie haben noch kein Passwort festgelegt!</string>
<!--AttachmentTypeSelectorAdapter-->
<!--ConversationItem-->
<string name="ConversationItem_message_size_d_kb">Größe der Nachricht: %d KB</string>
<string name="ConversationItem_expires_s">Läuft ab in: %s</string>
@@ -338,6 +339,7 @@
<string name="conversation__menu_add_attachment">Anhang hinzufügen</string>
<string name="conversation__menu_delete_thread">Thread löschen</string>
<string name="conversation__menu_compare">Vergleichen</string>
<!--conversation_group_options-->
<!--key_scanning-->
<string name="key_scanning__menu_compare">Vergleichen</string>
<string name="key_scanning__menu_get_scanned_to_compare">Zum Vergleich eingelesen werden</string>
@@ -353,5 +355,8 @@
<string name="text_secure_normal__menu_clear_passphrase">Passwort löschen</string>
<!--verify_keys-->
<string name="verify_keys__menu_verified">Verifiziert</string>
<!--Misc. piggybacking-->
<!--EOF-->
<string name="conversation_item_sent__download">Download</string>
<string name="conversation_item_received__download">Download</string>
</resources>

View File

@@ -35,6 +35,7 @@
<string name="ApplicationPreferenceActivity_you_need_to_have_entered_your_passphrase_before_importing_keys">Necesita ingresar una frase de contraseña antes de importar claves...</string>
<string name="ApplicationPreferenceActivity_you_need_to_have_entered_your_passphrase_before_managing_keys">Necesita ingresar su frase de contraseña antes de gestionar sus claves...</string>
<string name="ApplicationPreferenceActivity_you_havent_set_a_passphrase_yet">¡No ha elegido aún una frase de contraseña!</string>
<!--AttachmentTypeSelectorAdapter-->
<!--ConversationItem-->
<string name="ConversationItem_message_size_d_kb">Tamaño del mensaje: %d KB</string>
<string name="ConversationItem_expires_s">Expira: %s</string>
@@ -338,6 +339,7 @@
<string name="conversation__menu_add_attachment">Añadir archivo adjunto</string>
<string name="conversation__menu_delete_thread">Borrar conversación</string>
<string name="conversation__menu_compare">Comparar</string>
<!--conversation_group_options-->
<!--key_scanning-->
<string name="key_scanning__menu_compare">Comparar</string>
<string name="key_scanning__menu_get_scanned_to_compare">Escanear mi clave para comparar</string>
@@ -353,5 +355,6 @@
<string name="text_secure_normal__menu_clear_passphrase">Eliminar frase de contraseña</string>
<!--verify_keys-->
<string name="verify_keys__menu_verified">Verificado</string>
<!--Misc. piggybacking-->
<!--EOF-->
</resources>

371
res/values-fr/strings.xml Normal file
View File

@@ -0,0 +1,371 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="app_name">TextSecure</string>
<string name="yes">Oui</string>
<string name="no">Non</string>
<string name="delete">Effacer</string>
<!--ApplicationExportManager-->
<string name="ApplicationExportManager_import_database_and_settings_title">Importer la base de données et les paramètres ?</string>
<string name="ApplicationExportManager_import_database_and_settings_message">Importer la base de données, les clés et les paramètres de TextSecure depuis la carte SD ?\n\nAVERTISSEMENT: Cela détruira tous les messages, clés et paramètres existant !</string>
<string name="ApplicationExportManager_importing_database_and_keys">Importation de la base de données et des clés</string>
<string name="ApplicationExportManager_importing_your_sms_database_keys_and_settings">Importation de votre base de données SMS, des clés et des paramètres…</string>
<string name="ApplicationExportManager_export_database_question">Exporter la base de données ?</string>
<string name="ApplicationExportManager_export_textsecure_database_keys_and_settings_prompt">Exporter la base de données TextSecure, les clés et les paramètres vers la carte SD ?</string>
<string name="ApplicationExportManager_exporting_database_and_keys">Exportation de la base de donnée et des clés</string>
<string name="ApplicationExportManager_exporting_your_sms_database_keys_and_settings">Exportation de votre base de données SMS, des clés et des paramètres…</string>
<string name="ApplicationExportManager_no_sd_card_found_exclamation">Aucune carte SD trouvée !</string>
<string name="ApplicationExportManager_error_exporting_to_sd_exclamation">Erreur dexportation vers carte SD !</string>
<string name="ApplicationExportManager_import_successful_exclamation">Importation réussie !</string>
<string name="ApplicationExportManager_export_successful_exclamation">Exportation réussie !</string>
<string name="ApplicationExportManager_import">Importer</string>
<string name="ApplicationExportManager_export">Exporter</string>
<!--ApplicationMigrationManager-->
<string name="ApplicationMigrationManager_migrating_database">Migration de la base de données</string>
<string name="ApplicationMigrationManager_migrating_text_message_database">Migration de la base de données des messages…</string>
<string name="ApplicationMigrationManager_copy_system_text_message_database_question">Copier la base de données de messages du système ?</string>
<string name="ApplicationMigrationManager_copy_system_text_message_database_explanation">TextSecure utilise une base de données chiffrée qui est séparée de la base de données par défaut du système. Voulez-vous copier vos messages existant dans la base de données chiffrée de TextSecure ? Votre base de données par défaut ne sera pas affectée.</string>
<string name="ApplicationMigrationManager_copy">Copier</string>
<string name="ApplicationMigrationManager_dont_copy">Ne pas copier</string>
<!--ApplicationPreferencesActivity-->
<string name="ApplicationPreferenceActivity_not_found_exclamation">Introuvable !</string>
<string name="ApplicationPreferenceActivity_no_valid_identity_key_was_found_in_the_specified_contact">Aucune clé didentité valide na été trouvée dans le contact indiqué.</string>
<string name="ApplicationPreferenceActivity_you_don_t_have_an_identity_key_exclamation">Vous navez aucune clé didentité !</string>
<string name="ApplicationPreferenceActivity_you_have_not_yet_defined_a_contact_for_yourself">Vous n\'avez pas défini de contact pour vous même. Choisissez en un dans le menu Paramètres.</string>
<string name="ApplicationPreferenceActivity_exported_to_contacts_database">Exporté dans la base de données de contacts!</string>
<string name="ApplicationPreferenceActivity_you_need_to_have_entered_your_passphrase_before_importing_keys">Il est nécessaire davoir entré votre phrase de passe avant dimporter des clés…</string>
<string name="ApplicationPreferenceActivity_you_need_to_have_entered_your_passphrase_before_managing_keys">Il est nécessaire davoir entré votre phrase de passe avant de gérer les clés…</string>
<string name="ApplicationPreferenceActivity_you_havent_set_a_passphrase_yet">Vous navez pas encore défini de phrase de passe !</string>
<!--AttachmentTypeSelectorAdapter-->
<string name="AttachmentTypeSelectorAdapter_picture">Image</string>
<string name="AttachmentTypeSelectorAdapter_video">Vidéo</string>
<string name="AttachmentTypeSelectorAdapter_audio">Audio</string>
<!--ConversationItem-->
<string name="ConversationItem_message_size_d_kb">Taille du message : %d KB</string>
<string name="ConversationItem_expires_s">Expiration : %s</string>
<string name="ConversationItem_error_sending_message">Erreur denvoi du message</string>
<string name="ConversationItem_sending">Envoi…</string>
<string name="ConversationItem_saving_attachment">Sauver la pièce jointe</string>
<string name="ConversationItem_saving_attachment_to_sd_card">Enregistrement de pièce jointe sur la carte SD…</string>
<string name="ConversationItem_save_to_sd_card">Enregistrer sur la carte SD ?</string>
<string name="ConversationItem_this_media_has_been_stored_in_an_encrypted_database_warning">Ce média est stocké dans une base de données chiffrée. La version que vous sauvez sur carte SD ne sera plus chiffrée, voulez-vous continuer ?</string>
<string name="ConversationItem_error_while_saving_attachment_to_sd_card">Erreur denregistrement de la pièce jointe sur la carte SD !</string>
<string name="ConversationItem_success_exclamation">Réussite !</string>
<string name="ConversationItem_unable_to_write_to_sd_card_exclamation">Impossible décrire sur la carte SD !</string>
<string name="ConversationItem_view_secure_media_question">Visualiser le média sécurisé ?</string>
<string name="ConversationItem_this_media_has_been_stored_in_an_encrypted_database_external_viewer_warning">Ce média est stocké dans une base de données chiffrée. Malheureusement, le voir avec un visualisateur de contenu externe nécessite pour le moment que les données soient temporairement déchiffrées et écrites sur le disque. Êtes-vous sûr de vouloir faire cela ?</string>
<string name="ConversationItem_key_exchange_message">Message déchange de clés</string>
<string name="ConversationItem_received_and_processed_key_exchange_message">Message déchange de clés reçu et traité.</string>
<string name="ConversationItem_error_received_stale_key_exchange_message">Erreur, un message déchange de clés périmé a été reçu.</string>
<string name="ConversationItem_received_key_exchange_message_click_to_process">Message déchange de clés reçu, cliquez pour le traiter</string>
<!--ConversationActivity-->
<string name="ConversationActivity_initiate_secure_session_question">Initier une session sécurisée ?</string>
<string name="ConversationActivity_initiate_secure_session_with_s_question">Initer une session sécurisée avec %s ?</string>
<string name="ConversationActivity_abort_secure_session_confirmation">Confirmer l\'interruption de la session sécurisée</string>
<string name="ConversationActivity_are_you_sure_that_you_want_to_abort_this_secure_session_question">Êtes-vous sûr de vouloir interrompre cette session sécurisée ?</string>
<string name="ConversationActivity_delete_thread_confirmation">Confirmation de la suppression du fil</string>
<string name="ConversationActivity_are_you_sure_that_you_want_to_permanently_delete_this_conversation_question">Êtes-vous sûr de vouloir supprimer définitivement cette conversation ?</string>
<string name="ConversationActivity_add_attachment">Ajouter une pièce jointe</string>
<string name="ConversationActivity_compose_message">Rédiger le message</string>
<string name="ConversationActivity_sorry_there_was_an_error_setting_your_attachment">Désolé, il y a eu une erreur en ajoutant votre pièce jointe.</string>
<string name="ConversationActivity_sorry_the_selected_video_exceeds_message_size_restrictions">Désolé, la vidéo sélectionnée dépasse les restrictions de taille du message.</string>
<string name="ConversationActivity_sorry_the_selected_audio_exceeds_message_size_restrictions">Désolé, le fichier audio sélectionné dépasse les restrictions de taille du message.</string>
<string name="ConversationActivity_recipient_is_not_a_valid_sms_or_email_address_exclamation">Le destinataire nest pas une adresse SMS ou e-mail valide !</string>
<string name="ConversationActivity_message_is_empty_exclamation">Le message est vide !</string>
<string name="ConversationActivity_forward_message_prefix">FWD</string>
<string name="ConversationActivity_group_conversation_recipients">Destinataires Conversation de Groupe</string>
<string name="ConversationActivity_group_conversation">Conversation de Groupe</string>
<string name="ConversationActivity_d_recipients_in_group">%d destinataires dans le groupe</string>
<!--ConversationFragment-->
<string name="ConversationFragment_message_details">Détails du message</string>
<string name="ConversationFragment_sender_s_transport_s_sent_received_s">Expéditeur : %1$s\nTransport : %2$s\nEnvoyé / Reçu : %3$s</string>
<string name="ConversationFragment_confirm_message_delete">Confirmer la suppression du message</string>
<string name="ConversationFragment_are_you_sure_you_want_to_permanently_delete_this_message">Êtes-vous sûr de vouloir supprimer définitivement ce message ?</string>
<!--ConversationListAdapter-->
<string name="ConversationListAdapter_encrypted_message_enter_passphrase">Message chiffré, entrez la phrase de passe…</string>
<string name="ConversationListAdapter_key_exchange_message">Message déchange de clés…</string>
<!--ConversationListFragment-->
<string name="ConversationListFragment_delete_threads_question">Supprimer les fils ?</string>
<string name="ConversationListFragment_are_you_sure_you_wish_to_delete_all_selected_conversation_threads">Êtes-vous sûr de vouloir supprimer TOUS les fils de discussion sélectionnés ?</string>
<!--ConversationListItem-->
<string name="ConversationListItem_key_exchange_message">Message déchange de clés…</string>
<!--KeyScanningActivity-->
<string name="KeyScanningActivity_no_scanned_key_found_exclamation">Aucune clé scannée na été trouvée !</string>
<!--PassphraseChangeActivity-->
<string name="PassphraseChangeActivity_passphrases_dont_match_exclamation">Les phrases de passe ne correspondent pas !</string>
<string name="PassphraseChangeActivity_incorrect_old_passphrase_exclamation">Ancienne phrase de passe incorrecte !</string>
<!--PassphraseCreateActivity-->
<string name="PassphraseCreateActivity_passphrases_dont_match_exclamation">Les phrases de passe ne correspondent pas !</string>
<string name="PassphraseCreateActivity_generating_keypair">Génération de la KeyPair</string>
<string name="PassphraseCreateActivity_generating_a_local_encryption_keypair">Génération dune paire de clés locale de chiffrement…</string>
<!--PassphrasePromptActivity-->
<string name="PassphrasePromptActivity_invalid_passphrase_exclamation">Phrase de passe incorrecte !</string>
<!--ReceiveKeyActivity-->
<string name="ReceiveKeyActivity_error_you_have_received_a_corrupted_public_key">ERREUR:\n\nVous avez reçu une clef publique corrompue. Cette clef est inutilisable, veuillez renouveler la session sécurisée.</string>
<string name="ReceiveKeyActivity_error_you_have_received_a_public_key_from_an_unsupported_version_of_the_protocol">ERREUR:\n\nVous avez reçu une clef publique issue d\'une version de protocole non supportée. Cette clef est inutilisable, veuillez renouveler la session sécurisée.</string>
<string name="ReceiveKeyActivity_this_key_exchange_message_does_not_include_an_identity_signature">Ce message d\'échange de clef ne contient aucune signature d\'identité</string>
<string name="ReceiveKeyActivity_this_key_exchange_message_includes_an_identity_signature_but_you_do_not_yet_trust_it">Ce message d\'échange de clefs inclus une signature d\'identité, mais vous ne l\'avez pas encore approuvée.</string>
<string name="ReceiveKeyActivity_this_key_exchange_message_includes_an_identity_signature_which_you_trust_for_s">Ce message d\'échange de clefs inclus une signature d\'identité approuvée de: %s</string>
<string name="ReceiveKeyActivity_this_is_the_key_that_you_sent_to_start_your_current_encrypted_session_with_s">Ceci est la clef que vous avez envoyé pour initier une session sécurisée avec %s</string>
<string name="ReceiveKeyActivity_this_is_the_key_that_you_received_to_start_your_current_encrypted_session_with_s">Ceci est la clef que vous avez reçue pour initier une session sécurisée avec %s</string>
<string name="ReceiveKeyActivity_you_have_received_a_key_exchange_message_from_s_warning_you_already_have_an_encrypted_session">Vous avez reçu un message d\'échange de clefs de %s.\n\nATTENTION: Vous avez déjà une session cryptée avec ce contact. Si vous choisissez d\'accepter cet échange de clefs, votre session actuelle sera détruite et vous devrez vous authentifier de nouveau. Voulez vous compléter cet échange de clefs?</string>
<string name="ReceiveKeyActivity_you_have_received_a_key_exchange_message_from_s_you_have_previously_initiated">Vous avez reçu un message d\'échange de clefs de %s. Vous avez déjà commencé une session avec ce contact, et en acceptant cette clef, vous allez compléter l\'échange de clef. Voulez vous compléter cet échange de clefs?</string>
<string name="ReceiveKeyActivity_you_have_initiated_a_key_exchange_message_with_s_but_have_not_yet_received_a_reply">Vous avez commencé un échange de clefs avec %s mais n\'avez toujours pas reçu de réponse.</string>
<string name="ReceiveKeyActivity_you_have_received_a_key_exchange_message_from_s_you_have_no_existing_session">Vous avez reçu un message d\'échange de clefs de %s. Vous n\'avez pas encore de session avec ce contact, voulez vous compléter cet échange de clefs?</string>
<!--ReviewIdentitiesActivity-->
<string name="ReviewIdentitiesActivity_unable_to_view_corrupted_identity_key_exclamation">Impossible de visualiser une clé didentité corrompue !</string>
<string name="ReviewIdentitiesActivity_delete_identity">Supprimer lidentité ?</string>
<string name="ReviewIdentitiesActivity_delete_identity_are_you_sure_you_want_to_delete_this_identity_key">Êtes-vous sûr de vouloir supprimer définitivement cette clef didentité ?</string>
<string name="ReviewIdentitiesActivity_invalid_identity">Identité invalide !</string>
<!--SaveIdentityActivity-->
<string name="SaveIdentityActivity_you_must_specify_a_name_for_this_identity_exclamation">Vous devez renseigner un nom pour cette identité !</string>
<string name="SaveIdentityActivity_identity_name_exists_exclamation">Le nom didentité existe !</string>
<string name="SaveIdentityActivity_an_identity_key_with_the_specified_name_already_exists">Une identité avec le nom renseigné existe déjà.</string>
<string name="SaveIdentityActivity_manage_identities">Gérer les identités</string>
<!--VerifyIdentityActivity-->
<string name="VerifyIdentityActivity_mark_identity_verified_question">Marquer l\'identité comme vérifiée?</string>
<string name="VerifyIdentityActivity_are_you_sure_you_have_validated_the_recipients_identity_fingerprint_and_would_like_to_mark_it_as_verified">Êtes vous sûr de reconnaître l\'empreinte d\'identité du destinataire et la marquer comme vérifiée?</string>
<string name="VerifyIdentityActivity_mark_verified">Marquer comme vérifié</string>
<string name="VerifyIdentityActivity_you_do_not_have_an_identity_key">Vous navez pas de clé didentité.</string>
<string name="VerifyIdentityActivity_recipient_has_no_identity_key">Le destinataire na pas de clé didentité.</string>
<string name="VerifyIdentityActivity_recipient_has_no_identity_key_exclamation">Le destinataire na pas de clé didentité !</string>
<string name="VerifyIdentityActivity_scan_their_key_to_compare">Analyser leur clef pour comparer</string>
<string name="VerifyIdentityActivity_get_my_key_scanned">Analyser mes clefs</string>
<string name="VerifyIdentityActivity_warning_the_scanned_key_does_not_match_please_check_the_fingerprint_text_carefully">ATTENTION, les clefs scannées sont DIFFERENTES. Veuillez vérifier scrupuleusement le texte de l\'empreinte de clefs.</string>
<string name="VerifyIdentityActivity_not_verified_exclamation">NON vérifié !</string>
<string name="VerifyIdentityActivity_their_key_is_correct_it_is_also_necessary_to_verify_your_key_with_them_as_well">Leur clef est correcte. Il est aussi nécessaire de vérifier votre clef avec la leur</string>
<string name="VerifyIdentityActivity_verified_exclamation">Vérifié !</string>
<string name="VerifyIdentityActivity_you_don_t_have_an_identity_key_exclamation">Vous navez aucune clé didentité !</string>
<!--VerifyImportedIdentityActivity-->
<string name="VerifyImportedIdentityActivity_you_must_specify_a_name_for_this_contact_exclamation">Vous devez donner un nom à ce contact !</string>
<string name="VerifyImportedIdentityActivity_save_identity_key_question">Enregistrer la clef d\'identité ?</string>
<string name="VerifyImportedIdentityActivity_error_saving_identity_key_exclamation">Erreur d\'enregistrement de la clef d\'identité</string>
<string name="VerifyImportedIdentityActivity_this_identity_key_or_an_identity_key_with_the_same_name_already_exists_please_edit_your_key_database">Cette clef d\'identité ou une clef soociée au même nom existe déjà. Veuillez éditer votre base de données de clefs.</string>
<string name="VerifyImportedIdentityActivity_scan_to_compare">Analyser pour comparer</string>
<string name="VerifyImportedIdentityActivity_get_scanned_to_compare">Être analysé pour comparer</string>
<string name="VerifyImportedIdentityActivity_not_verified_exclamation">NON vérifié !</string>
<string name="VerifyImportedIdentityActivity_warning_the_scanned_key_does_not_match_exclamation">ATTENTION: les clefs analysées ne sont CORRESPONDENT PAS!</string>
<string name="VerifyImportedIdentityActivity_the_scanned_key_matches_exclamation">Les clefs analysées correspondent.</string>
<string name="VerifyImportedIdentityActivity_verified_exclamation">Vérifié !</string>
<string name="VerifyImportedIdentityActivity_are_you_sure_that_you_would_like_to_mark_this_as_a_valid_identity_key_for_all_future_correspondence_with_s">Êtes vous sûr de vouloir valider cette clef d\'identité pour tout échange futur avec %s ? Vous ne devriez faire cela que si vous avez effectivement vérifié cette empreinte.</string>
<!--VerifyKeysActivity-->
<string name="VerifyKeysActivity_mark_session_verified_question">Marquer cette session comme vérifiée?</string>
<string name="VerifyKeysActivity_are_you_sure_that_you_have_validated_these_fingerprints_and_would_like_to_mark_this_session_as_verified">Êtes vous sûr d\'avoir validé ces empreintes et désirez marquer cette session comme vérifiée?</string>
<string name="VerifyKeysActivity_mark_verified">Marquer comme vérifié</string>
<string name="VerifyKeysActivity_get_my_fingerprint_scanned">Analyser mon empreinte</string>
<string name="VerifyKeysActivity_scan_their_fingerprint">Analyser leur empreinte</string>
<string name="VerifyKeysActivity_warning_the_scanned_key_does_not_match_please_check_the_fingerprint_text_carefully2">ATTENTION: les clefs analysées ne correspondent PAS! Veuillez vérifier scrupuleusement le texte de l\'empreinte</string>
<string name="VerifyKeysActivity_not_verified_exclamation">NON vérifié !</string>
<string name="VerifyKeysActivity_their_key_is_correct_it_is_also_necessary_to_get_your_fingerprint_scanned_as_well">Leur clef est valide. Il est aussi nécessaire d\'analyser votre empreinte.</string>
<string name="VerifyKeysActivity_verified_exclamation">Vérifié !</string>
<!--ViewIdentityActivity-->
<string name="ViewIdentityActivity_you_do_not_have_an_identity_key">Vous navez pas de clé didentité.</string>
<string name="ViewIdentityActivity_scan_to_compare">Analyser pour comparer</string>
<string name="ViewIdentityActivity_get_scanned_to_compare">Être analysé pour comparer</string>
<string name="ViewIdentityActivity_warning_the_scanned_key_does_not_match_exclamation">ATTENTION, les clefs analysées ne correspondent PAS!</string>
<string name="ViewIdentityActivity_not_verified_exclamation">NON vérifié !</string>
<string name="ViewIdentityActivity_the_scanned_key_matches_exclamation">Les clefs analysées correspondent!</string>
<string name="ViewIdentityActivity_verified_exclamation">Vérifié !</string>
<!--KeyExchangeInitiator-->
<string name="KeyExchangeInitiator_initiate_despite_existing_request_question">Initier malgré la demande existante ?</string>
<string name="KeyExchangeInitiator_youve_already_sent_a_session_initiation_request_to_this_recipient_are_you_sure">Vous avez déjà envoyé une demande dinitiation de session à ce destinataire, êtes-vous sûr de vouloir en envoyer une autre ? Cela invalidera la première demande.</string>
<string name="KeyExchangeInitiator_send">Envoyer</string>
<!--MessageDisplayHelper-->
<string name="MessageDisplayHelper_bad_encrypted_message">Message mal chiffré…</string>
<string name="MessageDisplayHelper_decrypting_please_wait">Déchiffrage, veuillez patienter…</string>
<string name="MessageDisplayHelper_message_encrypted_for_non_existing_session">Message chiffré pour une session non-existante…</string>
<string name="MessageDisplayHelper_decryption_error_local_message_corrupted_mac_doesn_t_match_potential_tampering_question">Erreur de déchiffrage : message local corrompu, la MAC ne correspond pas. Tentative de manipulation ?</string>
<!--MmsDatabase-->
<string name="MmsDatabase_connecting_to_mms_server">Connexion au serveur MMS…</string>
<string name="MmsDatabase_downloading_mms">Téléchargement du MMS…</string>
<string name="MmsDatabase_mms_download_failed">Échec de téléchargement du MMS !</string>
<string name="MmsDatabase_downloading">Téléchargement</string>
<string name="MmsDatabase_anonymous">Anonyme</string>
<!--MmsMessageRecord-->
<string name="MmsMessageRecord_decrypting_mms_please_wait">Déchiffrage du MMS, veuillez patienter…</string>
<string name="MmsMessageRecord_bad_encrypted_mms_message">Message MMS mal chiffré…</string>
<string name="MmsMessageRecord_mms_message_encrypted_for_non_existing_session">Message MMS chiffré pour une session non-existante…</string>
<!--ApplicationMigrationService-->
<string name="ApplicationMigrationService_migrating">Migration</string>
<string name="ApplicationMigrationService_migrating_system_text_messages">Migration des messages texte du système</string>
<!--KeyCachingService-->
<string name="KeyCachingService_textsecure_passphrase_cached">Phrase de passe TextSecure en cache</string>
<string name="KeyCachingService_passphrase_cached">Phrase de passe en cache</string>
<!--MessageNotifier-->
<string name="MessageNotifier_d_new_messages">(%d) Nouveaux messages</string>
<string name="MessageNotifier_d_new_messages_most_recent_from_s">(%1$d) Nouveaux messages, le dernier est de : %2$s</string>
<string name="MessageNotifier_most_recent_from_s">Dernier de : %s</string>
<!--auto_initiate_activity-->
<string name="auto_initiate_activity__you_have_received_a_message_from_someone_who_supports_textsecure_encrypted_sessions_would_you_like_to_initiate_a_secure_session">Vous avez reçu un message de quelqu\'un qui utilise les sessions chiffrées TextSecure. Voulez vous commencer une session chiffrée avec lui?</string>
<string name="auto_initiate_activity__initiate_exchange">Commencer l\'échange</string>
<!--change_passphrase_activity-->
<string name="change_passphrase_activity__old_passphrase">Ancienne phrase de passe :</string>
<string name="change_passphrase_activity__new_passphrase">Nouvelle phrase de passe :</string>
<string name="change_passphrase_activity__repeat_new_passphrase">Répéter la nouvelle phrase de passe :</string>
<!--contact_selection_group_activity-->
<!--contact_selection_list_activity-->
<string name="contact_selection_group_activity__no_contacts">Aucun contact.</string>
<!--ContactSelectionListFragment-->
<string name="ContactSelectionlistFragment_select_for">Sélectionner pour</string>
<!--contact_selection_recent_activity-->
<string name="contact_selection_recent_activity__no_recent_calls">Aucun appel récent.</string>
<!--conversation_activity-->
<string name="conversation_activity__type_message">Entrez le message</string>
<string name="conversation_activity__send">Envoyer</string>
<string name="conversation_activity__remove">Supprimer</string>
<!--conversation_fragment_cab-->
<string name="conversation_fragment_cab__batch_selection_mode">Mode de sélection par lot</string>
<!--create_passphrase_activity-->
<string name="create_passphrase_activity__please_choose_a_passphrase_that_will_be_used_to_locally_encrypt_your_data_this_should_be_a_strong_passphrase">Veuillez choisir une phrase de passe pour chiffrer localement vos données. Cette phrase de passe doit être complexe.</string>
<string name="create_passphrase_activity__passphrase">Phrase de passe :</string>
<string name="create_passphrase_activity__repeat">Répétez :</string>
<!--receive_key_activity-->
<string name="receive_key_activity__session">Session</string>
<string name="receive_key_activity__identities">Identités</string>
<string name="receive_key_activity__complete_exchange">Finaliser l\'échange</string>
<!--recipients_panel-->
<string name="recipients_panel__to">À</string>
<!--review_identities-->
<string name="review_identities__you_don_t_currently_have_any_identity_keys_in_your_trust_database">Vous n\'avez pour linstant pas de clef d\'identité dans votre base de données de confiance.</string>
<!--save_identity_activity-->
<string name="save_identity_activity__identity_name">Nom de l\'identité :</string>
<!--verify_identity_activity-->
<string name="verify_identity_activity__their_identity_they_read">Leur identité (ce qu\'ils lisent) :</string>
<string name="verify_identity_activity__your_identity_you_read">Votre identité (ce que vous lisez) :</string>
<!--verify_import_identity_activity-->
<string name="verify_import_identity_activity__identity_name_n">Nom d\'identité :\\n</string>
<string name="verify_import_identity_activity__imported_identity_n">Identité importée :\\n</string>
<string name="verify_import_identity_activity__verified">Vérifié !</string>
<string name="verify_import_identity_activity__compare">Comparer</string>
<!--verify_keys_activity-->
<string name="verify_keys_activity__they_read_this">Ils lisent ceci :</string>
<string name="verify_keys_activity__you_read_this">Vous lisez ceci :</string>
<!--view_identity_activity-->
<string name="view_identity_activity__identity">Identité</string>
<string name="view_identity_activity__qr_code">QR Code</string>
<!--AndroidManifest.xml-->
<string name="AndroidManifest__create_passphrase">Créer une phrase de passe</string>
<string name="AndroidManifest__enter_passphrase">Saisir la phrase de passe</string>
<string name="AndroidManifest__select_contacts">Sélectionner des contacts</string>
<string name="AndroidManifest__textsecure_detected">TextSecure détecté</string>
<string name="AndroidManifest__public_identity_key">Clef d\'Identité Publique</string>
<string name="AndroidManifest__change_passphrase">Modifier la phrase de passe</string>
<string name="AndroidManifest__verify_session">Vérifier la session</string>
<string name="AndroidManifest__verify_identity">Vérifier lidentité</string>
<string name="AndroidManifest__save_identity">Sauvegarder lidentité</string>
<string name="AndroidManifest__manage_identity_keys">Gérer les clefs d\'identité</string>
<string name="AndroidManifest__complete_key_exchange">Finaliser l\'échange de clefs</string>
<string name="AndroidManifest__verify_imported_identity">Vérifier lidentité importée</string>
<!--preferences.xml-->
<string name="preferences__use_settings">Paramètres dutilisation</string>
<string name="preferences__pref_all_sms_title">Utiliser pour tous les SMS</string>
<string name="preferences__pref_all_mms_title">Utiliser pour tous les MMS</string>
<string name="preferences__use_textsecure_for_viewing_and_storing_all_incoming_text_messages">Utiliser TextSecure pour consulter et stocker tous les messages texte entrant ?</string>
<string name="preferences__use_textsecure_for_viewing_and_storing_all_incoming_multimedia_messages">Utiliser TextSecure pour consulter et stocker tous les messages multimédia entrant ?</string>
<string name="preferences__input_settings">Paramètres de saisie</string>
<string name="preferences__pref_enter_sends_title">La touche Entrée envoie</string>
<string name="preferences__pressing_the_enter_key_will_send_text_messages">La touche entrée enverra les messages texte</string>
<string name="preferences__display_settings">Paramètres d\'affichage</string>
<string name="preferences__choose_identity">Choisir une identité</string>
<string name="preferences__choose_your_contact_entry_from_the_contacts_list">Choisissez votre entrée de contact dans le répertoire</string>
<string name="preferences__encryption_settings">Paramètres de chiffrement</string>
<string name="preferences__change_passphrase">Modifier le Mot de Passe</string>
<string name="preferences__change_my_passphrase">Modifier mon mot de passe</string>
<string name="preferences__complete_key_exchanges">Finaliser l\'échange de clefs</string>
<string name="preferences__automatically_complete_key_exchanges_for_new_sessions_or_for_existing_sessions_with_the_same_identity_key">Finaliser automatiquement les échanges de clés pour les sessions, nouvelles ou existantes, avec la même clef d\'identité.</string>
<string name="preferences__include_a_whitespace_tag_at_the_end_of_every_non_encrypted_message">Ajouter un caractère espace vide à la fin de tous les messages en clair</string>
<string name="preferences__include_whitespace_tag">Ajouter un caractère espace vide</string>
<string name="preferences__sign_key_exchange_messages_with_identity_key">Signer le message d\'échange de clés avec la clef d\'identité</string>
<string name="preferences__sign_key_exchange">Signer l\'échange de clés</string>
<string name="preferences__forget_passphrase_from_memory_after_some_interval">Oublier le mot de passe en mémoire après une certaine durée</string>
<string name="preferences__timeout_passphrase">Expiration du mot de passe</string>
<string name="preferences__pref_timeout_interval_dialogtitle">Choisir la durée de validité du mot de passe</string>
<string name="preferences__pref_timeout_interval_title">Durée d\'expiration</string>
<string name="preferences__the_amount_of_time_to_wait_before_forgetting_passphrase">Durée avant doublier le mot de passe en mémoire</string>
<string name="preferences__identity_key_settings">Paramètres des clés d\'identité</string>
<string name="preferences__view_my_identity_key">Afficher ma clé d\'identité</string>
<string name="preferences__export_my_identity_key">Exporter ma clé d\'identité</string>
<string name="preferences__import_contacts_key">Importer la clé du contact</string>
<string name="preferences__import_an_identity_key_from_a_contact">Importer une clé d\'identité depuis un contact</string>
<string name="preferences__manage_identity_keys">Gérer les clés d\'identité</string>
<string name="preferences__manage_configured_identity_keys">Gérer les clés d\'identité configurées</string>
<string name="preferences__notification_settings">Paramètres de notification</string>
<string name="preferences__notifications">Notifications</string>
<string name="preferences__display_message_notifications_in_status_bar">Afficher les notifications dans la barre de statut</string>
<string name="preferences__led_color">Couleur de la LED</string>
<string name="preferences__change_notification_led_color">Modifier la couleur de la LED de notification</string>
<string name="preferences__pref_led_blink_title">Rythme de clignotement de la LED</string>
<string name="preferences__change_notification_blink_pattern">Modifier le rythme de clignotement de la LED de notification</string>
<string name="preferences__pref_led_blink_dialogtitle">Choisir le rythme de clignotement de la LED</string>
<string name="preferences__select_led_color">Choisir la couleur de la LED</string>
<string name="preferences__select_ringtone">Choisir une sonnerie</string>
<string name="preferences__vibrate">Vibrer</string>
<string name="preferences__also_vibrate_when_notified">Vibrer aussi lors des notifications</string>
<string name="preferences__minutes">minutes</string>
<string name="preferences__hours">heures</string>
<string name="preferences__green">Vert</string>
<string name="preferences__red">Rouge</string>
<string name="preferences__blue">Bleu</string>
<string name="preferences__orange">Orange</string>
<string name="preferences__cyan">Cyan</string>
<string name="preferences__magenta">Magenta</string>
<string name="preferences__fast">Rapide</string>
<string name="preferences__normal">Normal</string>
<string name="preferences__slow">Lent</string>
<string name="preferences__custom">Personnalisé</string>
<!--****************************************-->
<!--menus-->
<!--****************************************-->
<!--contact_selection_list-->
<string name="contact_selection_list__menu_select_all">Sélectionner tout</string>
<string name="contact_selection_list__menu_unselect_all">Désélectionner tout</string>
<!--contact_selection-->
<string name="contact_selection__menu_finished">Terminé</string>
<!--conversation_button_context-->
<string name="conversation_button_context__menu_send_unencrypted">Envoyer en clair</string>
<!--conversation_callable-->
<string name="conversation_callable__menu_call">Appeler</string>
<!--conversation_context-->
<string name="conversation_context__menu_message_details">Détails du message</string>
<string name="conversation_context__menu_copy_text">Copier le texte</string>
<string name="conversation_context__menu_delete_message">Supprimer le message</string>
<string name="conversation_context__menu_forward_message">Faire suivre le message</string>
<!--conversation_insecure-->
<string name="conversation_insecure__menu_start_secure_session">Commencer une session sécurisée</string>
<!--conversation_list_batch-->
<string name="conversation_list_batch__menu_delete_selected">Supprimer la sélection</string>
<string name="conversation_list_batch__menu_select_all">Sélectionner tout</string>
<!--conversation_list-->
<string name="conversation_list__menu_search">Chercher</string>
<!--conversation_secure_verified-->
<!--conversation_secure_unverified-->
<string name="conversation_secure_verified__menu_security">Sécurité</string>
<string name="conversation_secure_verified__menu_verify_session">Vérifier la Session</string>
<string name="conversation_secure_verified__menu_verify_recipient">Vérifier le Destinataire</string>
<string name="conversation_secure_verified__menu_abort_secure_session">Interrompre la session sécurisée</string>
<!--conversation-->
<string name="conversation__menu_add_attachment">Ajouter une pièce jointe</string>
<string name="conversation__menu_delete_thread">Supprimer le fil de discussion</string>
<string name="conversation__menu_compare">Comparer</string>
<!--conversation_group_options-->
<string name="convesation_group_options__recipients_list">Liste des destinataires</string>
<!--key_scanning-->
<string name="key_scanning__menu_compare">Comparer</string>
<string name="key_scanning__menu_get_scanned_to_compare">Être analysé pour comparer</string>
<string name="key_scanning__menu_scan_to_compare">Analyser pour comparer</string>
<!--text_secure_locked-->
<string name="text_secure_locked__menu_unlock">Débloquer</string>
<!--text_secure_normal-->
<string name="text_secure_normal__menu_new_message">Nouveau Message</string>
<string name="text_secure_normal__menu_settings">Paramètres</string>
<string name="text_secure_normal__menu_import_export">Importer / Exporter</string>
<string name="text_secure_normal__menu_import">Importer</string>
<string name="text_secure_normal__menu_export">Exporter</string>
<string name="text_secure_normal__menu_clear_passphrase">Oublier le mot de passe</string>
<!--verify_keys-->
<string name="verify_keys__menu_verified">Vérifié</string>
<!--Misc. piggybacking-->
<string name="PlayStoreListing">TextSecure est une application de messagerie texte avec une sécurité améliorée, qui constitue une alternative complète à lapplication de messagerie texte par défaut. Les messages à dautres utilisateurs de TextSecure sont chiffrés à la volée, et tous les messages sont stockés dans une base de données chiffrée sur lappareil. Si votre téléphone est perdu ou volé, vos messages seront en sécurité, et les communications avec dautres utilisateurs de TextSecure ne peuvent pas être espionnées à la volée.</string>
<!--EOF-->
<string name="conversation_item_sent__download">Télécharger</string>
<string name="conversation_item_received__download">Télécharger</string>
</resources>

View File

@@ -35,6 +35,7 @@
<string name="ApplicationPreferenceActivity_you_need_to_have_entered_your_passphrase_before_importing_keys">Devi inserire la tua password prima di importare le chiavi...</string>
<string name="ApplicationPreferenceActivity_you_need_to_have_entered_your_passphrase_before_managing_keys">Devi inserire la tua password prima di controllare le chiavi...</string>
<string name="ApplicationPreferenceActivity_you_havent_set_a_passphrase_yet">Non hai ancora impostato una password!</string>
<!--AttachmentTypeSelectorAdapter-->
<!--ConversationItem-->
<string name="ConversationItem_message_size_d_kb">Dimensione Messaggio: %d KB</string>
<string name="ConversationItem_expires_s">Scadenza: %s</string>
@@ -338,6 +339,7 @@
<string name="conversation__menu_add_attachment">Aggiungi allegato</string>
<string name="conversation__menu_delete_thread">Cancella conversazione</string>
<string name="conversation__menu_compare">Controlla</string>
<!--conversation_group_options-->
<!--key_scanning-->
<string name="key_scanning__menu_compare">Controlla</string>
<string name="key_scanning__menu_get_scanned_to_compare">Scansionato per controllare</string>
@@ -353,5 +355,6 @@
<string name="text_secure_normal__menu_clear_passphrase">Cancella password</string>
<!--verify_keys-->
<string name="verify_keys__menu_verified">Verificato</string>
<!--Misc. piggybacking-->
<!--EOF-->
</resources>

374
res/values-nl/strings.xml Normal file
View File

@@ -0,0 +1,374 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="app_name">TextSecure</string>
<string name="yes">Ja</string>
<string name="no">Nee</string>
<string name="delete">Verwijder</string>
<!--ApplicationExportManager-->
<string name="ApplicationExportManager_import_database_and_settings_title">Importeer Database en Instellingen?</string>
<string name="ApplicationExportManager_import_database_and_settings_message">Importeer TextSecure database, sleutels en instellingen vanaf de SD-kaart?\n\nWAARSCHUWING: Dit kan bestaande berichten, sleutels en instellingen aantasten!</string>
<string name="ApplicationExportManager_importing_database_and_keys">Importeren van Database en Sleutels</string>
<string name="ApplicationExportManager_importing_your_sms_database_keys_and_settings">SMS database, sleutels en instellingen aan het importeren...</string>
<string name="ApplicationExportManager_export_database_question">Exporteer Database?</string>
<string name="ApplicationExportManager_export_textsecure_database_keys_and_settings_prompt">Exporteer TextSecure database, sleutels en instellingen naar de SD-kaart?</string>
<string name="ApplicationExportManager_exporting_database_and_keys">Exporteren van Database en Sleutels</string>
<string name="ApplicationExportManager_exporting_your_sms_database_keys_and_settings">SMS database, sleutels en instellingen aan het exporteren...</string>
<string name="ApplicationExportManager_no_sd_card_found_exclamation">Geen SD-kaart gevonden!</string>
<string name="ApplicationExportManager_error_exporting_to_sd_exclamation">Fout bij het exporteren naar SD!</string>
<string name="ApplicationExportManager_import_successful_exclamation">Importeren Succesvol!</string>
<string name="ApplicationExportManager_export_successful_exclamation">Exporteren Succesvol!</string>
<string name="ApplicationExportManager_import">Importeer</string>
<string name="ApplicationExportManager_export">Exporteer</string>
<!--ApplicationMigrationManager-->
<string name="ApplicationMigrationManager_migrating_database">Migreren Database</string>
<string name="ApplicationMigrationManager_migrating_text_message_database">Berichten database aan het migreren...</string>
<string name="ApplicationMigrationManager_copy_system_text_message_database_question">Kopieer Systeem Tekstberichten Database?</string>
<string name="ApplicationMigrationManager_copy_system_text_message_database_explanation">TextSecure gebruikt een versleutelde database die afgescheiden is van de standaard systeemdatabase. Wilt u uw bestaande tekstberichten kopiëren naar TextSecure\'s versleutelde database? Uw standaard systeemdatabase blijft onaangetast.</string>
<string name="ApplicationMigrationManager_copy">Kopiëren</string>
<string name="ApplicationMigrationManager_dont_copy">Niet kopiëren</string>
<!--ApplicationPreferencesActivity-->
<string name="ApplicationPreferencesActivity_currently_s">Momenteel: %s</string>
<string name="ApplicationPreferenceActivity_not_found_exclamation">Niet gevonden!</string>
<string name="ApplicationPreferenceActivity_no_valid_identity_key_was_found_in_the_specified_contact">Geen geldige identiteitssleutel gevonden bij de gespecificeerde contactpersoon.</string>
<string name="ApplicationPreferenceActivity_you_don_t_have_an_identity_key_exclamation">U heeft geen identiteitssleutel!</string>
<string name="ApplicationPreferenceActivity_you_have_not_yet_defined_a_contact_for_yourself">U heeft nog geen contact voor uzelf ingesteld! Selecteer deze in het Instellingen menu.</string>
<string name="ApplicationPreferenceActivity_exported_to_contacts_database">Geëxporteerd naar contactendatabase!</string>
<string name="ApplicationPreferenceActivity_you_need_to_have_entered_your_passphrase_before_importing_keys">U moet uw wachtwoord opgegeven voor het importeren van sleutels...</string>
<string name="ApplicationPreferenceActivity_you_need_to_have_entered_your_passphrase_before_managing_keys">U moet uw wachtwoord opgegeven voor het beheren van sleutels...</string>
<string name="ApplicationPreferenceActivity_you_havent_set_a_passphrase_yet">U heeft nog geen wachtwoord ingesteld!</string>
<!--AttachmentTypeSelectorAdapter-->
<string name="AttachmentTypeSelectorAdapter_picture">Afbeelding</string>
<string name="AttachmentTypeSelectorAdapter_video">Video</string>
<string name="AttachmentTypeSelectorAdapter_audio">Audio</string>
<!--ConversationItem-->
<string name="ConversationItem_message_size_d_kb">Bericht grootte: %d KB</string>
<string name="ConversationItem_expires_s">Verloopt: %s</string>
<string name="ConversationItem_error_sending_message">Fout bij versturen bericht</string>
<string name="ConversationItem_sending">Versturen...</string>
<string name="ConversationItem_saving_attachment">Bijlage Opslaan</string>
<string name="ConversationItem_saving_attachment_to_sd_card">Bijlage aan het opslaan op de SD-kaart...</string>
<string name="ConversationItem_save_to_sd_card">Opslaan op SD-kaart?</string>
<string name="ConversationItem_this_media_has_been_stored_in_an_encrypted_database_warning">Deze media is opgeslagen in een versleutelde database. De versie die u opslaat op de SD-kaart is niet langer versleuteld, wilt u doorgaan?</string>
<string name="ConversationItem_error_while_saving_attachment_to_sd_card">Fout tijdens het opslaan van de bijlage naar de SD-kaart!</string>
<string name="ConversationItem_success_exclamation">Succesvol!</string>
<string name="ConversationItem_unable_to_write_to_sd_card_exclamation">SD-kaart niet beschrijfbaar!</string>
<string name="ConversationItem_view_secure_media_question">Bekijk veilige media?</string>
<string name="ConversationItem_this_media_has_been_stored_in_an_encrypted_database_external_viewer_warning">Deze media is opgeslagen in een versleutelde database. Deze data moet helaas tijdelijk ontsleuteld en naar de opslagruimte geschrijven worden om deze te bekijken met een externe applicatie. Weet u zeker dat u dit wilt?</string>
<string name="ConversationItem_key_exchange_message">Sleuteluitwisselingsbericht</string>
<string name="ConversationItem_received_and_processed_key_exchange_message">Sleuteluitwisselingsbericht ontvangen en verwerkt.</string>
<string name="ConversationItem_error_received_stale_key_exchange_message">Fout, verouderd steuteluitwisselingsbericht ontvangen.</string>
<string name="ConversationItem_received_key_exchange_message_click_to_process">Sleuteluitwisselingsbericht ontvangen, klik hier om te verwerken</string>
<!--ConversationActivity-->
<string name="ConversationActivity_initiate_secure_session_question">Initieer veilige sessie?</string>
<string name="ConversationActivity_initiate_secure_session_with_s_question">Initieer veilige sessie met %s?</string>
<string name="ConversationActivity_abort_secure_session_confirmation">Bevestiging Afbreken Beveiligde Sessie</string>
<string name="ConversationActivity_are_you_sure_that_you_want_to_abort_this_secure_session_question">Weet u zeker dat u deze beveiligde sessie wilt stopzetten?</string>
<string name="ConversationActivity_delete_thread_confirmation">Bevestiging Verwijderen Conversatie</string>
<string name="ConversationActivity_are_you_sure_that_you_want_to_permanently_delete_this_conversation_question">Weet u zeker dat u deze conversatie permanent wilt verwijderen?</string>
<string name="ConversationActivity_add_attachment">Voeg bijlage toe</string>
<string name="ConversationActivity_compose_message">Bericht Opstellen</string>
<string name="ConversationActivity_sorry_there_was_an_error_setting_your_attachment">Excuses, er trad een fout op bij het instellen van uw bijlage.</string>
<string name="ConversationActivity_sorry_the_selected_video_exceeds_message_size_restrictions">Excuses, de geselecteerde video overschrijdt de berichtgrootte restricties.</string>
<string name="ConversationActivity_sorry_the_selected_audio_exceeds_message_size_restrictions">Excuses, de geselecteerde audio overschrijdt de berichtgrootte restricties.</string>
<string name="ConversationActivity_recipient_is_not_a_valid_sms_or_email_address_exclamation">Ontvanger is niet een geldig SMS of e-mailadres!</string>
<string name="ConversationActivity_message_is_empty_exclamation">Bericht is leeg!</string>
<string name="ConversationActivity_forward_message_prefix">FWD</string>
<string name="ConversationActivity_group_conversation_recipients">Groepsconversatie Ontvangers</string>
<string name="ConversationActivity_group_conversation">Groepsconversatie</string>
<string name="ConversationActivity_d_recipients_in_group">%d ontvangers in groep</string>
<!--ConversationFragment-->
<string name="ConversationFragment_message_details">Bericht details</string>
<string name="ConversationFragment_sender_s_transport_s_sent_received_s">Verzender: %1$s\nTransport: %2$s\nVerstuurd/Ontvangen:%3$s</string>
<string name="ConversationFragment_confirm_message_delete">Bevestig Bericht Verwijdering</string>
<string name="ConversationFragment_are_you_sure_you_want_to_permanently_delete_this_message">Weet u zeker dat u dit bericht permanent wilt verwijderen?</string>
<!--ConversationListAdapter-->
<string name="ConversationListAdapter_encrypted_message_enter_passphrase">Versleuteld bericht, voer wachtwoord in...</string>
<string name="ConversationListAdapter_key_exchange_message">Sleuteluitwisselingsbericht...</string>
<!--ConversationListFragment-->
<string name="ConversationListFragment_delete_threads_question">Meerdere conversaties verwijderen?</string>
<string name="ConversationListFragment_are_you_sure_you_wish_to_delete_all_selected_conversation_threads">Weet u zeker dat u ALLE geselecteerde conversaties wilt verwijderen?</string>
<!--ConversationListItem-->
<string name="ConversationListItem_key_exchange_message">Sleuteluitwisselingsbericht...</string>
<!--KeyScanningActivity-->
<string name="KeyScanningActivity_no_scanned_key_found_exclamation">Geen gescande sleutel gevonden!</string>
<!--PassphraseChangeActivity-->
<string name="PassphraseChangeActivity_passphrases_dont_match_exclamation">Wachtwoorden komen niet overeen!</string>
<string name="PassphraseChangeActivity_incorrect_old_passphrase_exclamation">Incorrect oud wachtwoord!</string>
<!--PassphraseCreateActivity-->
<string name="PassphraseCreateActivity_passphrases_dont_match_exclamation">Wachtwoorden komen niet overeen!</string>
<string name="PassphraseCreateActivity_generating_keypair">Sleutelpaar Genereren</string>
<string name="PassphraseCreateActivity_generating_a_local_encryption_keypair">Lokaal encryptie sleutelpaar aan het genereren...</string>
<!--PassphrasePromptActivity-->
<string name="PassphrasePromptActivity_invalid_passphrase_exclamation">Ongeldig Wachtwoord!</string>
<!--ReceiveKeyActivity-->
<string name="ReceiveKeyActivity_error_you_have_received_a_corrupted_public_key">FOUT: U heeft een corrupte publieke sleutel ontvangen. Deze sleutel kan niet verwerkt worden, herinitiëer alstublieft een beveiligde sessie.</string>
<string name="ReceiveKeyActivity_error_you_have_received_a_public_key_from_an_unsupported_version_of_the_protocol">FOUT: U heeft een publieke sleutel ontvangen van een niet ondersteunde versie van het protocol. Deze sleutel kan niet verwerkt worden, herinitiëer alstublieft een beveiligde sessie.</string>
<string name="ReceiveKeyActivity_this_key_exchange_message_does_not_include_an_identity_signature">Dit sleuteluitwisselingsbericht bevat geen identiteitshandtekening.</string>
<string name="ReceiveKeyActivity_this_key_exchange_message_includes_an_identity_signature_but_you_do_not_yet_trust_it">Dit sleuteluitwisselingsbericht bevat een identiteitshandtekening, maar u vertrouwt deze nog niet.</string>
<string name="ReceiveKeyActivity_this_key_exchange_message_includes_an_identity_signature_which_you_trust_for_s">Dit sleuteluitwisselingsbericht bevat een identiteitshandtekening welke u vertrouwt voor: %s</string>
<string name="ReceiveKeyActivity_this_is_the_key_that_you_sent_to_start_your_current_encrypted_session_with_s">Dit is de sleutel die u verzond om de huidige versleutelde sessie te starten met %s</string>
<string name="ReceiveKeyActivity_this_is_the_key_that_you_received_to_start_your_current_encrypted_session_with_s">Dit is de sleutel die u ontving om de huidige versleutelde sessie te starten met %s</string>
<string name="ReceiveKeyActivity_you_have_received_a_key_exchange_message_from_s_warning_you_already_have_an_encrypted_session">U heeft een Sleuteluitwisselingsbericht ontvangen van %s. \n\nWAARSCHUWING: U heeft reeds een versleutelde sessie met deze contactpersoon. Indien u kiest om dit sleuteluitwisselingsbericht te accepteren, stopt uw huidige sessie en moet u opnieuw authentificeren. Wilt u deze sleuteluitwisseling voltooien?</string>
<string name="ReceiveKeyActivity_you_have_received_a_key_exchange_message_from_s_you_have_previously_initiated">U heeft een Sleuteluitwisselingsbericht ontvangen van %s. U heeft voorheen een sessie geïnitieerd met deze contactpersoon en door deze sleutel te accepteren voltooid u de sleuteluitwisseling. Wilt u de sleuteluitwisseling voltooien?</string>
<string name="ReceiveKeyActivity_you_have_initiated_a_key_exchange_message_with_s_but_have_not_yet_received_a_reply">U heeft een Sleuteluitwisselingsbericht verstuurd naar %s en heeft nog geen reactie ontvangen.</string>
<string name="ReceiveKeyActivity_you_have_received_a_key_exchange_message_from_s_you_have_no_existing_session">U heeft een Sleuteluitwisselingsbericht ontvangen van %s. U heeft geen bestaande sessie met deze contactpersoon, wilt u deze sleuteluitwisseling voltooien?</string>
<!--ReviewIdentitiesActivity-->
<string name="ReviewIdentitiesActivity_unable_to_view_corrupted_identity_key_exclamation">Niet in staat de corrupte identiteitssleutel te bekijken!</string>
<string name="ReviewIdentitiesActivity_delete_identity">Verwijder identiteit?</string>
<string name="ReviewIdentitiesActivity_delete_identity_are_you_sure_you_want_to_delete_this_identity_key">Weet u zeker dat u deze identiteitssleutel permanent wilt verwijderen?</string>
<string name="ReviewIdentitiesActivity_invalid_identity">Ongeldige Identiteit!</string>
<!--SaveIdentityActivity-->
<string name="SaveIdentityActivity_you_must_specify_a_name_for_this_identity_exclamation">U moet een naam opgeven voor deze identiteit!</string>
<string name="SaveIdentityActivity_identity_name_exists_exclamation">Identiteitsnaam bestaat al!</string>
<string name="SaveIdentityActivity_an_identity_key_with_the_specified_name_already_exists">Een identiteitssleutel met de gespecificeerde naam bestaat reeds.</string>
<string name="SaveIdentityActivity_manage_identities">Beheer Identiteiten</string>
<!--VerifyIdentityActivity-->
<string name="VerifyIdentityActivity_mark_identity_verified_question">Markeer Identiteit Als Geverifieerd?</string>
<string name="VerifyIdentityActivity_are_you_sure_you_have_validated_the_recipients_identity_fingerprint_and_would_like_to_mark_it_as_verified">Weet u zeker dat u de handtekening van de identiteit van de ontvanger valide is en u deze als geverifieerd wilt markeren?</string>
<string name="VerifyIdentityActivity_mark_verified">Markeer Geverifieerd</string>
<string name="VerifyIdentityActivity_you_do_not_have_an_identity_key">U heeft geen identiteitssleutel.</string>
<string name="VerifyIdentityActivity_recipient_has_no_identity_key">De ontvanger heeft geen identiteitssleutel.</string>
<string name="VerifyIdentityActivity_recipient_has_no_identity_key_exclamation">De ontvanger heeft geen identiteitssleutel!</string>
<string name="VerifyIdentityActivity_scan_their_key_to_compare">Scan de sleutel om te vergelijken</string>
<string name="VerifyIdentityActivity_get_my_key_scanned">Laat mijn sleutel scannen</string>
<string name="VerifyIdentityActivity_warning_the_scanned_key_does_not_match_please_check_the_fingerprint_text_carefully">WAARSCHUWING, de gescande sleutel komt NIET overeen! Controleert u alstublieft de vingerafdruk zorgvuldig.</string>
<string name="VerifyIdentityActivity_not_verified_exclamation">NIET Geverifieerd!</string>
<string name="VerifyIdentityActivity_their_key_is_correct_it_is_also_necessary_to_verify_your_key_with_them_as_well">Sleutel partner is correct. Het is ook nodig om uw sleutel door de partner te laten verifiëren.</string>
<string name="VerifyIdentityActivity_verified_exclamation">Geverifieerd</string>
<string name="VerifyIdentityActivity_you_don_t_have_an_identity_key_exclamation">U heeft geen identiteitssleutel!</string>
<!--VerifyImportedIdentityActivity-->
<string name="VerifyImportedIdentityActivity_you_must_specify_a_name_for_this_contact_exclamation">U moet een naam opgeven voor dit contact!</string>
<string name="VerifyImportedIdentityActivity_save_identity_key_question">Identiteitssleutel Opslaan?</string>
<string name="VerifyImportedIdentityActivity_error_saving_identity_key_exclamation">Fout bij opslaan identiteitssleutel!</string>
<string name="VerifyImportedIdentityActivity_this_identity_key_or_an_identity_key_with_the_same_name_already_exists_please_edit_your_key_database">Deze identiteitssleutel of een identiteitssleutel met dezelfde naam bestaat reeds. Bewerk alstublieft uw sleuteldatabase.</string>
<string name="VerifyImportedIdentityActivity_scan_to_compare">Scan om te vergelijken</string>
<string name="VerifyImportedIdentityActivity_get_scanned_to_compare">Wordt gescand om te vergelijken</string>
<string name="VerifyImportedIdentityActivity_not_verified_exclamation">NIET geverifieerd!</string>
<string name="VerifyImportedIdentityActivity_warning_the_scanned_key_does_not_match_exclamation">WAARSCHUWING, de gescande sleutel komt NIET overeen!</string>
<string name="VerifyImportedIdentityActivity_the_scanned_key_matches_exclamation">De gescande sleutel komt overeen!</string>
<string name="VerifyImportedIdentityActivity_verified_exclamation">Geverifieerd</string>
<string name="VerifyImportedIdentityActivity_are_you_sure_that_you_would_like_to_mark_this_as_a_valid_identity_key_for_all_future_correspondence_with_s">Weet u zeker dat u deze identiteitssleutel als valide wilt markeren voor toekomstige correspondentie met %s? U zou dit alleen moeten doen als u de vingerafdruk geverifieerd heeft.</string>
<!--VerifyKeysActivity-->
<string name="VerifyKeysActivity_mark_session_verified_question">Markeer Sessie Als Geverifieerd?</string>
<string name="VerifyKeysActivity_are_you_sure_that_you_have_validated_these_fingerprints_and_would_like_to_mark_this_session_as_verified">Weet u zeker dat u deze vingerafdrukken gevalideerd heeft en deze sessie als geverifiëerd wilt markeren?</string>
<string name="VerifyKeysActivity_mark_verified">Markeer Geverifieerd</string>
<string name="VerifyKeysActivity_get_my_fingerprint_scanned">Mijn sleutel vingerafdruk laten scannen</string>
<string name="VerifyKeysActivity_scan_their_fingerprint">Scan sleutel vingerafdruk partner</string>
<string name="VerifyKeysActivity_warning_the_scanned_key_does_not_match_please_check_the_fingerprint_text_carefully2">WAARSCHUWING, de gescande sleutel komt NIET overeen! Controleert u alstublieft de vingerafdruk zorgvuldig.</string>
<string name="VerifyKeysActivity_not_verified_exclamation">NIET Geverifieerd!</string>
<string name="VerifyKeysActivity_their_key_is_correct_it_is_also_necessary_to_get_your_fingerprint_scanned_as_well">De sleutel van de gesprekspartner is correct. Het is ook nodig om uw sleutel vingerafdruk te laten scannen.</string>
<string name="VerifyKeysActivity_verified_exclamation">Geverifieerd!</string>
<!--ViewIdentityActivity-->
<string name="ViewIdentityActivity_you_do_not_have_an_identity_key">U heeft geen identiteitssleutel.</string>
<string name="ViewIdentityActivity_scan_to_compare">Scan om te vergelijken</string>
<string name="ViewIdentityActivity_get_scanned_to_compare">Wordt gescand om te vergelijken</string>
<string name="ViewIdentityActivity_warning_the_scanned_key_does_not_match_exclamation">WAARSCHUWING, de gescande sleutel komt NIET overeen!</string>
<string name="ViewIdentityActivity_not_verified_exclamation">NIET Geverifieerd!</string>
<string name="ViewIdentityActivity_the_scanned_key_matches_exclamation">De gescande sleutel komt overeen!</string>
<string name="ViewIdentityActivity_verified_exclamation">Geverifieerd!</string>
<!--KeyExchangeInitiator-->
<string name="KeyExchangeInitiator_initiate_despite_existing_request_question">Initiëren Ondanks Bestaand Verzoek?</string>
<string name="KeyExchangeInitiator_youve_already_sent_a_session_initiation_request_to_this_recipient_are_you_sure">U heeft reeds een sessieinititatieverzoek verstuurd naar deze ontvanger, weet u zeker dat u er nog een wilt sturen? Dit maakt het eerdere verzoek ongeldig.</string>
<string name="KeyExchangeInitiator_send">Verstuur</string>
<!--MessageDisplayHelper-->
<string name="MessageDisplayHelper_bad_encrypted_message">Verkeerd versleuteld bericht...</string>
<string name="MessageDisplayHelper_decrypting_please_wait">Ontsleutelen, een moment...</string>
<string name="MessageDisplayHelper_message_encrypted_for_non_existing_session">Bericht ontsleuteld voor niet bestaande sessie...</string>
<string name="MessageDisplayHelper_decryption_error_local_message_corrupted_mac_doesn_t_match_potential_tampering_question">Onsleutelingsfout: lokaal bericht beschadigd, MAC komt niet overeen. Mogelijke sabotage?</string>
<!--MmsDatabase-->
<string name="MmsDatabase_connecting_to_mms_server">Verbinden met MMS server...</string>
<string name="MmsDatabase_downloading_mms">Downloaden van MMS...</string>
<string name="MmsDatabase_mms_download_failed">MMS Download mislukt!</string>
<string name="MmsDatabase_downloading">Downloaden...</string>
<string name="MmsDatabase_anonymous">Anoniem</string>
<!--MmsMessageRecord-->
<string name="MmsMessageRecord_decrypting_mms_please_wait">MMS ontsleutelen, een moment...</string>
<string name="MmsMessageRecord_bad_encrypted_mms_message">Verkeerd versleuteld MMS-bericht...</string>
<string name="MmsMessageRecord_mms_message_encrypted_for_non_existing_session">MMS-bericht versleuteld voor niet bestaande sessie...</string>
<!--ApplicationMigrationService-->
<string name="ApplicationMigrationService_migrating">Migreren</string>
<string name="ApplicationMigrationService_migrating_system_text_messages">Migreren Systeem Tekstberichten</string>
<!--KeyCachingService-->
<string name="KeyCachingService_textsecure_passphrase_cached">TextSecure Wachtwoord In Cache</string>
<string name="KeyCachingService_passphrase_cached">Wachtwoord In Cache</string>
<!--MessageNotifier-->
<string name="MessageNotifier_d_new_messages">(%d) Nieuwe berichten</string>
<string name="MessageNotifier_d_new_messages_most_recent_from_s">(%1$d) Nieuwe berichten, meest recente van: %2$s</string>
<string name="MessageNotifier_most_recent_from_s">Meest recente van: %s</string>
<!--auto_initiate_activity-->
<string name="auto_initiate_activity__you_have_received_a_message_from_someone_who_supports_textsecure_encrypted_sessions_would_you_like_to_initiate_a_secure_session">U heeft een bericht ontvangen van iemand die versleutelde TextSecure sessies ondersteunt. Wilt u een beveiligde sessie initiëren?</string>
<string name="auto_initiate_activity__initiate_exchange">Initieer Uitwisseling</string>
<!--change_passphrase_activity-->
<string name="change_passphrase_activity__old_passphrase">Oud wachtwoord:</string>
<string name="change_passphrase_activity__new_passphrase">Nieuw wachtwoord:</string>
<string name="change_passphrase_activity__repeat_new_passphrase">Herhaal nieuw wachtwoord:</string>
<!--contact_selection_group_activity-->
<!--contact_selection_list_activity-->
<string name="contact_selection_group_activity__no_contacts">Geen contacten.</string>
<!--ContactSelectionListFragment-->
<string name="ContactSelectionlistFragment_select_for">Selecteer voor</string>
<!--contact_selection_recent_activity-->
<string name="contact_selection_recent_activity__no_recent_calls">Geen recente gesprekken.</string>
<!--conversation_activity-->
<string name="conversation_activity__type_message">Typ bericht</string>
<string name="conversation_activity__send">Verstuur</string>
<string name="conversation_activity__remove">Verwijder</string>
<!--conversation_fragment_cab-->
<string name="conversation_fragment_cab__batch_selection_mode">Selecteer Meerdere Tegelijk</string>
<!--create_passphrase_activity-->
<string name="create_passphrase_activity__please_choose_a_passphrase_that_will_be_used_to_locally_encrypt_your_data_this_should_be_a_strong_passphrase">Kiest u alstublieft een wachtwoord welke gebruikt zal worden om lokaal uw data te versleutelen. Een sterk wachtwoord wordt aangeraden.</string>
<string name="create_passphrase_activity__passphrase">Wachtwoord:</string>
<string name="create_passphrase_activity__repeat">Herhaal:</string>
<!--receive_key_activity-->
<string name="receive_key_activity__session">Sessie</string>
<string name="receive_key_activity__identities">Identiteiten</string>
<string name="receive_key_activity__complete_exchange">Voltooi Uitwisseling</string>
<!--recipients_panel-->
<string name="recipients_panel__to">Aan</string>
<!--review_identities-->
<string name="review_identities__you_don_t_currently_have_any_identity_keys_in_your_trust_database">U heeft momenteel geen identiteitssleutels in uw vertrouwensdatabase.</string>
<!--save_identity_activity-->
<string name="save_identity_activity__identity_name">Identiteitsnaam:</string>
<!--verify_identity_activity-->
<string name="verify_identity_activity__their_identity_they_read">Identiteit partner (partner leest):</string>
<string name="verify_identity_activity__your_identity_you_read">Uw identiteit (u leest):</string>
<!--verify_import_identity_activity-->
<string name="verify_import_identity_activity__identity_name_n">Identiteitsnaam:\\n</string>
<string name="verify_import_identity_activity__imported_identity_n">Geïmporteerde identiteit:\\n</string>
<string name="verify_import_identity_activity__verified">Geverifieerd!</string>
<string name="verify_import_identity_activity__compare">Vergelijk</string>
<!--verify_keys_activity-->
<string name="verify_keys_activity__they_read_this">Partner leest dit:</string>
<string name="verify_keys_activity__you_read_this">U leest dit:</string>
<!--view_identity_activity-->
<string name="view_identity_activity__identity">Identiteit:</string>
<string name="view_identity_activity__qr_code">QR Code</string>
<!--AndroidManifest.xml-->
<string name="AndroidManifest__create_passphrase">Creëer Wachtwoord</string>
<string name="AndroidManifest__enter_passphrase">Voer Wachtwoord In</string>
<string name="AndroidManifest__select_contacts">Selecteer Contacten</string>
<string name="AndroidManifest__textsecure_detected">TextSecure gedetecteerd</string>
<string name="AndroidManifest__public_identity_key">Publieke Identiteitssleutel</string>
<string name="AndroidManifest__change_passphrase">Wachtwoord Veranderen</string>
<string name="AndroidManifest__verify_session">Verifieer Sessie</string>
<string name="AndroidManifest__verify_identity">Identiteit Verifiëren</string>
<string name="AndroidManifest__save_identity">Identiteit Opslaan</string>
<string name="AndroidManifest__manage_identity_keys">Identiteitssleutels Beheren</string>
<string name="AndroidManifest__complete_key_exchange">Sleuteluitwisseling Voltooien</string>
<string name="AndroidManifest__verify_imported_identity">Geïmporteerde Identiteit Verifiëren</string>
<!--preferences.xml-->
<string name="preferences__use_settings">Gebruiksinstellingen</string>
<string name="preferences__pref_all_sms_title">Gebruik voor alle SMS</string>
<string name="preferences__pref_all_mms_title">Gebruik voor alle MMS</string>
<string name="preferences__use_textsecure_for_viewing_and_storing_all_incoming_text_messages">Gebruik TextSecure voor het bekijken en opslaan van alle inkomende tekstberichten</string>
<string name="preferences__use_textsecure_for_viewing_and_storing_all_incoming_multimedia_messages">Gebruik TextSecure voor het bekijken en opslaan van alle inkomende multimediaberichten</string>
<string name="preferences__input_settings">Invoer Instellingen</string>
<string name="preferences__pref_enter_sends_title">Enter verstuurt</string>
<string name="preferences__pressing_the_enter_key_will_send_text_messages">De \'Enter\' toets gebruiken voor versturen tekstberichten</string>
<string name="preferences__display_settings">Beeldscherminstellingen</string>
<string name="preferences__choose_identity">Kies Identiteit</string>
<string name="preferences__choose_your_contact_entry_from_the_contacts_list">Kies uw contactinformatie uit de contactpersonenlijst.</string>
<string name="preferences__encryption_settings">Encryptie Instellingen</string>
<string name="preferences__change_passphrase">Wachtwoord Veranderen</string>
<string name="preferences__change_my_passphrase">Verander mijn wachtwoord</string>
<string name="preferences__complete_key_exchanges">Sleuteluitwisseling Voltooien</string>
<string name="preferences__automatically_complete_key_exchanges_for_new_sessions_or_for_existing_sessions_with_the_same_identity_key">Automatisch sleuteluitwisselingen voltooien voor nieuwe sessies of voor bestaande sessies met dezelfde identiteitssleutel</string>
<string name="preferences__include_a_whitespace_tag_at_the_end_of_every_non_encrypted_message">Op het einde van ieder onversleuteld bericht een spatie toevoegen</string>
<string name="preferences__include_whitespace_tag">Spatie toevoegen</string>
<string name="preferences__sign_key_exchange_messages_with_identity_key">Teken sleuteluitwisselingsberichten met identiteitssleutel</string>
<string name="preferences__sign_key_exchange">Onderteken Sleuteluitwisseling</string>
<string name="preferences__forget_passphrase_from_memory_after_some_interval">Vergeet het wachtwoord uit het geheugen na een bepaalde tijd</string>
<string name="preferences__timeout_passphrase">Time-out wachtwoord</string>
<string name="preferences__pref_timeout_interval_dialogtitle">Selecteer Wachtwoord Time-out</string>
<string name="preferences__pref_timeout_interval_title">Time-out interval</string>
<string name="preferences__the_amount_of_time_to_wait_before_forgetting_passphrase">Hoe lang te wachten voor het vergeten van het wachtwoord uit het geheugen</string>
<string name="preferences__identity_key_settings">Identiteitssleutel Instellingen</string>
<string name="preferences__view_my_identity_key">Bekijk mijn identiteitssleutel</string>
<string name="preferences__export_my_identity_key">Exporteer mijn identiteitssleutel</string>
<string name="preferences__import_contacts_key">Sleutel van contact importeren</string>
<string name="preferences__import_an_identity_key_from_a_contact">Importeer een identiteitssleutel van een contact</string>
<string name="preferences__manage_identity_keys">Beheer Identiteitssleutels</string>
<string name="preferences__manage_configured_identity_keys">Beheer ingestelde identiteitssleutels</string>
<string name="preferences__notification_settings">Notificatie instellingen</string>
<string name="preferences__notifications">Notificaties</string>
<string name="preferences__display_message_notifications_in_status_bar">Toon berichtmeldingen in notificatiebalk</string>
<string name="preferences__led_color">LED Kleur</string>
<string name="preferences__change_notification_led_color">Wijzig notificatie LED kleur</string>
<string name="preferences__pref_led_blink_title">LED Knipperpatroon</string>
<string name="preferences__change_notification_blink_pattern">Verander notificatie LED knipperpatroon</string>
<string name="preferences__pref_led_blink_dialogtitle">Selecteer LED Knipperpatroon</string>
<string name="preferences__select_led_color">Selecteer LED Kleur</string>
<string name="preferences__select_ringtone">Selecteer ringtone</string>
<string name="preferences__vibrate">Trillen</string>
<string name="preferences__also_vibrate_when_notified">Ook trillen bij notificaties</string>
<string name="preferences__minutes">minuten</string>
<string name="preferences__hours">uren</string>
<string name="preferences__green">Groen</string>
<string name="preferences__red">Rood</string>
<string name="preferences__blue">Blauw</string>
<string name="preferences__orange">Oranje</string>
<string name="preferences__cyan">Cyaan</string>
<string name="preferences__magenta">Magenta</string>
<string name="preferences__fast">Snel</string>
<string name="preferences__normal">Normaal</string>
<string name="preferences__slow">Langzaam</string>
<string name="preferences__custom">Aangepast</string>
<!--****************************************-->
<!--menus-->
<!--****************************************-->
<!--contact_selection_list-->
<string name="contact_selection_list__menu_select_all">Alles Selecteren</string>
<string name="contact_selection_list__menu_unselect_all">Alles Deselecteren</string>
<!--contact_selection-->
<string name="contact_selection__menu_finished">Voltooid</string>
<!--conversation_button_context-->
<string name="conversation_button_context__menu_send_unencrypted">Verstuur onversleuteld</string>
<!--conversation_callable-->
<string name="conversation_callable__menu_call">Bel</string>
<!--conversation_context-->
<string name="conversation_context__menu_message_details">Bericht details</string>
<string name="conversation_context__menu_copy_text">Tekst kopiëren</string>
<string name="conversation_context__menu_delete_message">Bericht verwijderen</string>
<string name="conversation_context__menu_forward_message">Bericht doorsturen</string>
<!--conversation_insecure-->
<string name="conversation_insecure__menu_start_secure_session">Begin Beveiligde Sessie</string>
<!--conversation_list_batch-->
<string name="conversation_list_batch__menu_delete_selected">Verwijder Geselecteerde</string>
<string name="conversation_list_batch__menu_select_all">Alles Selecteren</string>
<!--conversation_list-->
<string name="conversation_list__menu_search">Zoeken</string>
<!--conversation_secure_verified-->
<!--conversation_secure_unverified-->
<string name="conversation_secure_verified__menu_security">Veiligheid</string>
<string name="conversation_secure_verified__menu_verify_session">Verifieer Sessie</string>
<string name="conversation_secure_verified__menu_verify_recipient">Verifieer Ontvanger</string>
<string name="conversation_secure_verified__menu_abort_secure_session">Stop Beveiligde Sessie</string>
<!--conversation-->
<string name="conversation__menu_add_attachment">Voeg bijlage toe</string>
<string name="conversation__menu_delete_thread">Verwijder gehele gesprek</string>
<string name="conversation__menu_compare">Vergelijk</string>
<!--conversation_group_options-->
<string name="convesation_group_options__recipients_list">Lijst met ontvangers</string>
<!--key_scanning-->
<string name="key_scanning__menu_compare">Vergelijken</string>
<string name="key_scanning__menu_get_scanned_to_compare">Wordt gescand om te vergelijken</string>
<string name="key_scanning__menu_scan_to_compare">Scan om te vergelijken</string>
<!--text_secure_locked-->
<string name="text_secure_locked__menu_unlock">Ontgrendel</string>
<!--text_secure_normal-->
<string name="text_secure_normal__menu_new_message">Nieuw Bericht</string>
<string name="text_secure_normal__menu_settings">Instellingen</string>
<string name="text_secure_normal__menu_import_export">Importeer/Exporteer</string>
<string name="text_secure_normal__menu_import">Importeer</string>
<string name="text_secure_normal__menu_export">Exporteer</string>
<string name="text_secure_normal__menu_clear_passphrase">Vergrendelen</string>
<!--verify_keys-->
<string name="verify_keys__menu_verified">Geverifieerd</string>
<!--Misc. piggybacking-->
<string name="PlayStoreListing">TextSecure is een tekstberichtapplicatie met verbeterde beveiliging die dient als een volledige vervanger voor de standaard tekstberichtapplicatie. Berichten naar andere TextSecure gebruikers worden versleuteld en alle tekstberichten worden opgeslagen in een versleutelde database op uw telefoon. Wanneer uw telefoon kwijt of gestolen is, zijn uw berichten veilig en communicatie met andere TextSecure gebruikers kan niet afgeluisterd worden.</string>
<!--EOF-->
<string name="conversation_item_sent__download">Download</string>
<string name="conversation_item_sent__downloading">Downloaden</string>
<string name="conversation_item_received__download">Download</string>
<string name="conversation_item_received__downloading">Downloaden</string>
</resources>

View File

@@ -35,6 +35,10 @@
<string name="ApplicationPreferenceActivity_you_need_to_have_entered_your_passphrase_before_importing_keys">Du må taste inn passordet før du importerer nøkler...</string>
<string name="ApplicationPreferenceActivity_you_need_to_have_entered_your_passphrase_before_managing_keys">Du må taste inn passordet før du administrerer nøkler...</string>
<string name="ApplicationPreferenceActivity_you_havent_set_a_passphrase_yet">Du har ikke definert et passord ennå!</string>
<!--AttachmentTypeSelectorAdapter-->
<string name="AttachmentTypeSelectorAdapter_picture">Bilde</string>
<string name="AttachmentTypeSelectorAdapter_video">Video</string>
<string name="AttachmentTypeSelectorAdapter_audio">Audio</string>
<!--ConversationItem-->
<string name="ConversationItem_message_size_d_kb">Meldingsstørrelse: %d KB</string>
<string name="ConversationItem_expires_s">Utløper: %s</string>
@@ -67,6 +71,7 @@
<string name="ConversationActivity_sorry_the_selected_audio_exceeds_message_size_restrictions">Beklager, den valgte lydfilen overstiger begrensningene til meldingsstørrelse.</string>
<string name="ConversationActivity_recipient_is_not_a_valid_sms_or_email_address_exclamation">Mottakeren er ikke en gyldig SMS- eller e-postadresse!</string>
<string name="ConversationActivity_message_is_empty_exclamation">Meldingen er tom!</string>
<string name="ConversationActivity_forward_message_prefix">VS</string>
<!--ConversationFragment-->
<string name="ConversationFragment_message_details">Meldingsdetaljer</string>
<string name="ConversationFragment_sender_s_transport_s_sent_received_s">Sender: %1$s⏎ Transport: %2$s⏎ Sendt/Mottatt:%3$s</string>
@@ -292,21 +297,18 @@
<string name="preferences__select_ringtone">Velg varslingstone</string>
<string name="preferences__vibrate">Vibrasjon</string>
<string name="preferences__also_vibrate_when_notified">Også vibrere ved varsling</string>
<string name="preferences__minutes">minutes</string>
<string name="preferences__hours">hours</string>
<string name="preferences__green">Green</string>
<string name="preferences__red">Red</string>
<string name="preferences__blue">Blue</string>
<string name="preferences__minutes">minutter</string>
<string name="preferences__hours">timer</string>
<string name="preferences__green">Grønn</string>
<string name="preferences__red">Rød</string>
<string name="preferences__blue">Blå</string>
<string name="preferences__orange">Orange</string>
<string name="preferences__cyan">Cyan</string>
<string name="preferences__cyan">Turkis</string>
<string name="preferences__magenta">Magenta</string>
<string name="preferences__fast">Fast</string>
<string name="preferences__fast">Hurtig</string>
<string name="preferences__normal">Normal</string>
<string name="preferences__slow">Slow</string>
<string name="preferences__custom">Custom</string>
<string name="preferences__slow">Sakte</string>
<string name="preferences__custom">Tilpasset</string>
<!--****************************************-->
<!--menus-->
<!--****************************************-->
@@ -341,6 +343,7 @@
<string name="conversation__menu_add_attachment">Legg til vedlegg</string>
<string name="conversation__menu_delete_thread">Slett tråd</string>
<string name="conversation__menu_compare">Sammenlign</string>
<!--conversation_group_options-->
<!--key_scanning-->
<string name="key_scanning__menu_compare">Sammenlign</string>
<string name="key_scanning__menu_get_scanned_to_compare">Få den skannet for å sammenlikne</string>
@@ -356,5 +359,9 @@
<string name="text_secure_normal__menu_clear_passphrase">Fjern passord</string>
<!--verify_keys-->
<string name="verify_keys__menu_verified">Bekreftet</string>
<!--Misc. piggybacking-->
<string name="PlayStoreListing">TextSecure er en tekstmeldingsapplikasjon hvor sikkerheten er forbedret. TextSecure fungerer som en full erstatning for mobilterminalens standard SMS/MMS applikasjon. Meldinger til andre TextSecure brukere sendes kryptert, og alle meldinger lagres i en kryptert database på mobilterminalen. Dersom telefonen blir stjålet eller mistet, vil meldingene være trygge, og kommunikasjon med andre TextSecure brukere kan ikke overvåkes.</string>
<!--EOF-->
<string name="conversation_item_sent__download">Last ned</string>
<string name="conversation_item_received__download">Last ned</string>
</resources>

View File

@@ -35,6 +35,10 @@
<string name="ApplicationPreferenceActivity_you_need_to_have_entered_your_passphrase_before_importing_keys">Necessita de inserir a frase-chave antes de importar chaves...</string>
<string name="ApplicationPreferenceActivity_you_need_to_have_entered_your_passphrase_before_managing_keys">Necessita de inserir a frase-chave antes de gerir chaves...</string>
<string name="ApplicationPreferenceActivity_you_havent_set_a_passphrase_yet">Ainda não definiu uma frase-chave!</string>
<!--AttachmentTypeSelectorAdapter-->
<string name="AttachmentTypeSelectorAdapter_picture">Imagem</string>
<string name="AttachmentTypeSelectorAdapter_video">Vídeo</string>
<string name="AttachmentTypeSelectorAdapter_audio">Áudio</string>
<!--ConversationItem-->
<string name="ConversationItem_message_size_d_kb">Tamanho da mensagem: %d KB</string>
<string name="ConversationItem_expires_s">Expira: %s</string>
@@ -67,6 +71,7 @@
<string name="ConversationActivity_sorry_the_selected_audio_exceeds_message_size_restrictions">Lamento, o áudio seleccionado excede as restrições de tamanho da mensagem</string>
<string name="ConversationActivity_recipient_is_not_a_valid_sms_or_email_address_exclamation">Destinatário não é um endereço SMS ou email válido!</string>
<string name="ConversationActivity_message_is_empty_exclamation">A mensagem está vazia!</string>
<string name="ConversationActivity_forward_message_prefix">FWD</string>
<!--ConversationFragment-->
<string name="ConversationFragment_message_details">Detalhes da mensagem</string>
<string name="ConversationFragment_sender_s_transport_s_sent_received_s">Remetente: %1$s⏎ Transporte: %2$s⏎ Enviadas/Recebidas:%3$s</string>
@@ -338,6 +343,7 @@
<string name="conversation__menu_add_attachment">Adicionar anexo</string>
<string name="conversation__menu_delete_thread">Apagar conversa</string>
<string name="conversation__menu_compare">Comparar</string>
<!--conversation_group_options-->
<!--key_scanning-->
<string name="key_scanning__menu_compare">Comparar</string>
<string name="key_scanning__menu_get_scanned_to_compare">Apresente o código QR para comparar</string>
@@ -353,5 +359,6 @@
<string name="text_secure_normal__menu_clear_passphrase">Limpar frase-chave</string>
<!--verify_keys-->
<string name="verify_keys__menu_verified">Verificado</string>
<!--Misc. piggybacking-->
<!--EOF-->
</resources>

View File

@@ -30,6 +30,7 @@
<string name="ApplicationMigrationManager_dont_copy">Don\'t copy</string>
<!-- ApplicationPreferencesActivity -->
<string name="ApplicationPreferencesActivity_currently_s">Currently: %s</string>
<string name="ApplicationPreferenceActivity_not_found_exclamation">Not found!</string>
<string name="ApplicationPreferenceActivity_no_valid_identity_key_was_found_in_the_specified_contact">No valid identity key was found in the specified contact.</string>
<string name="ApplicationPreferenceActivity_you_don_t_have_an_identity_key_exclamation">You don\'t have an identity key!</string>
@@ -38,6 +39,12 @@
<string name="ApplicationPreferenceActivity_you_need_to_have_entered_your_passphrase_before_importing_keys">You need to have entered your passphrase before importing keys...</string>
<string name="ApplicationPreferenceActivity_you_need_to_have_entered_your_passphrase_before_managing_keys">You need to have entered your passphrase before managing keys...</string>
<string name="ApplicationPreferenceActivity_you_havent_set_a_passphrase_yet">You haven\'t set a passphrase yet!</string>
<!-- AttachmentTypeSelectorAdapter -->
<string name="AttachmentTypeSelectorAdapter_picture">Picture</string>
<string name="AttachmentTypeSelectorAdapter_video">Video</string>
<string name="AttachmentTypeSelectorAdapter_audio">Audio</string>
<!-- ConversationItem -->
<string name="ConversationItem_message_size_d_kb">Message size: %d KB</string>
@@ -72,6 +79,10 @@
<string name="ConversationActivity_sorry_the_selected_audio_exceeds_message_size_restrictions">Sorry, the selected audio exceeds message size restrictions.</string>
<string name="ConversationActivity_recipient_is_not_a_valid_sms_or_email_address_exclamation">Recipient is not a valid SMS or email address!</string>
<string name="ConversationActivity_message_is_empty_exclamation">Message is empty!</string>
<string name="ConversationActivity_forward_message_prefix">FWD</string>
<string name="ConversationActivity_group_conversation_recipients">Group Conversation Recipients</string>
<string name="ConversationActivity_group_conversation">Group Conversation</string>
<string name="ConversationActivity_d_recipients_in_group">%d recipients in group</string>
<!-- ConversationFragment -->
<string name="ConversationFragment_message_details">Message details</string>
@@ -397,6 +408,9 @@
<string name="conversation__menu_delete_thread">Delete thread</string>
<string name="conversation__menu_compare">Compare</string>
<!-- conversation_group_options -->
<string name="convesation_group_options__recipients_list">Recipients list</string>
<!-- key_scanning -->
<string name="key_scanning__menu_compare">Compare</string>
<string name="key_scanning__menu_get_scanned_to_compare">Get scanned to compare</string>
@@ -415,7 +429,14 @@
<!-- verify_keys -->
<string name="verify_keys__menu_verified">Verified</string>
<!-- Misc. piggybacking -->
<string name="PlayStoreListing">TextSecure is a security enhanced text messaging application that serves as a full replacement for the default text messaging application. Messages to other TextSecure users are encrypted over the air, and all text messages are stored in an encrypted database on the device. If your phone is lost or stolen, your messages will be safe, and communication with other TextSecure users can\'t be monitored over the air.</string>
<!-- EOF -->
<string name="conversation_item_sent__download">Download</string>
<string name="conversation_item_sent__downloading">Downloading</string>
<string name="conversation_item_received__download">Download</string>
<string name="conversation_item_received__downloading">Downloading</string>
</resources>

View File

@@ -38,7 +38,7 @@
android:title="@string/preferences__pref_enter_sends_title" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/preferences__display_settings">
<PreferenceCategory android:title="@string/preferences__display_settings" android:key="pref_display_category">
<Preference android:key="pref_choose_identity"
android:title="@string/preferences__choose_identity"

View File

@@ -23,12 +23,14 @@ import android.net.Uri;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceManager;
import android.provider.ContactsContract;
import android.widget.Toast;
import com.actionbarsherlock.app.SherlockPreferenceActivity;
import com.actionbarsherlock.view.MenuItem;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.contacts.ContactIdentityManager;
import org.thoughtcrime.securesms.crypto.IdentityKey;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.MasterSecret;
@@ -36,6 +38,8 @@ import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.MemoryCleaner;
import java.util.List;
/**
* The Activity for application preference display and management.
*
@@ -62,6 +66,8 @@ public class ApplicationPreferencesActivity extends SherlockPreferenceActivity {
public static final String PASSPHRASE_TIMEOUT_PREF = "pref_timeout_passphrase";
public static final String AUTO_KEY_EXCHANGE_PREF = "pref_auto_complete_key_exchange";
private static final String DISPLAY_CATEGORY_PREF = "pref_display_category";
private static final String VIEW_MY_IDENTITY_PREF = "pref_view_identity";
private static final String EXPORT_MY_IDENTITY_PREF = "pref_export_identity";
private static final String IMPORT_CONTACT_IDENTITY_PREF = "pref_import_identity";
@@ -76,12 +82,18 @@ public class ApplicationPreferencesActivity extends SherlockPreferenceActivity {
addPreferencesFromResource(R.xml.preferences);
this.findPreference(IDENTITY_PREF).setOnPreferenceClickListener(new IdentityPreferenceClickListener());
this.findPreference(VIEW_MY_IDENTITY_PREF).setOnPreferenceClickListener(new ViewMyIdentityClickListener());
this.findPreference(EXPORT_MY_IDENTITY_PREF).setOnPreferenceClickListener(new ExportMyIdentityClickListener());
this.findPreference(IMPORT_CONTACT_IDENTITY_PREF).setOnPreferenceClickListener(new ImportContactIdentityClickListener());
this.findPreference(MANAGE_IDENTITIES_PREF).setOnPreferenceClickListener(new ManageIdentitiesClickListener());
this.findPreference(CHANGE_PASSPHRASE_PREF).setOnPreferenceClickListener(new ChangePassphraseClickListener());
initializeIdentitySelection();
this.findPreference(VIEW_MY_IDENTITY_PREF)
.setOnPreferenceClickListener(new ViewMyIdentityClickListener());
this.findPreference(EXPORT_MY_IDENTITY_PREF)
.setOnPreferenceClickListener(new ExportMyIdentityClickListener());
this.findPreference(IMPORT_CONTACT_IDENTITY_PREF)
.setOnPreferenceClickListener(new ImportContactIdentityClickListener());
this.findPreference(MANAGE_IDENTITIES_PREF)
.setOnPreferenceClickListener(new ManageIdentitiesClickListener());
this.findPreference(CHANGE_PASSPHRASE_PREF)
.setOnPreferenceClickListener(new ChangePassphraseClickListener());
}
@Override
@@ -127,10 +139,38 @@ public class ApplicationPreferencesActivity extends SherlockPreferenceActivity {
return false;
}
private void initializeIdentitySelection() {
ContactIdentityManager identity = ContactIdentityManager.getInstance(this);
if (identity.isSelfIdentityAutoDetected()) {
Preference preference = this.findPreference(DISPLAY_CATEGORY_PREF);
this.getPreferenceScreen().removePreference(preference);
} else {
Uri contactUri = identity.getSelfIdentityUri();
if (contactUri != null) {
String contactName = ContactAccessor.getInstance().getNameFromContact(this, contactUri);
this.findPreference(IDENTITY_PREF)
.setSummary(String.format(getString(R.string.ApplicationPreferencesActivity_currently_s),
contactName));
}
this.findPreference(IDENTITY_PREF)
.setOnPreferenceClickListener(new IdentityPreferenceClickListener());
}
}
private void handleIdentitySelection(Intent data) {
Uri contactData = data.getData();
if (contactData != null)
PreferenceManager.getDefaultSharedPreferences(this).edit().putString(IDENTITY_PREF, contactData.toString()).commit();
Uri contactUri = data.getData();
if (contactUri != null) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
String contactUriString = contactUri.toString();
preferences.edit().putString(IDENTITY_PREF, contactUriString).commit();
initializeIdentitySelection();
}
}
private void importIdentityKey(Uri uri) {
@@ -154,7 +194,8 @@ public class ApplicationPreferencesActivity extends SherlockPreferenceActivity {
private class IdentityPreferenceClickListener implements Preference.OnPreferenceClickListener {
public boolean onPreferenceClick(Preference preference) {
Intent intent = ContactAccessor.getInstance().getIntentForContactSelection();
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType(ContactsContract.Contacts.CONTENT_TYPE);
startActivityForResult(intent, PICK_IDENTITY_CONTACT);
return true;
}
@@ -172,9 +213,6 @@ public class ApplicationPreferencesActivity extends SherlockPreferenceActivity {
private class ExportMyIdentityClickListener implements Preference.OnPreferenceClickListener {
public boolean onPreferenceClick(Preference preference) {
String contactUri = PreferenceManager.getDefaultSharedPreferences(ApplicationPreferencesActivity.this)
.getString(IDENTITY_PREF, null);
if (!IdentityKeyUtil.hasIdentityKey(ApplicationPreferencesActivity.this)) {
Toast.makeText(ApplicationPreferencesActivity.this,
R.string.ApplicationPreferenceActivity_you_don_t_have_an_identity_key_exclamation,
@@ -182,14 +220,18 @@ public class ApplicationPreferencesActivity extends SherlockPreferenceActivity {
return true;
}
if (contactUri == null) {
List<Long> rawContactIds = ContactIdentityManager
.getInstance(ApplicationPreferencesActivity.this)
.getSelfIdentityRawContactIds();
if (rawContactIds== null) {
Toast.makeText(ApplicationPreferencesActivity.this,
R.string.ApplicationPreferenceActivity_you_have_not_yet_defined_a_contact_for_yourself,
Toast.LENGTH_LONG).show();
return true;
}
ContactAccessor.getInstance().insertIdentityKey(ApplicationPreferencesActivity.this, Uri.parse(contactUri),
ContactAccessor.getInstance().insertIdentityKey(ApplicationPreferencesActivity.this, rawContactIds,
IdentityKeyUtil.getIdentityKey(ApplicationPreferencesActivity.this));
Toast.makeText(ApplicationPreferencesActivity.this,
@@ -205,7 +247,8 @@ public class ApplicationPreferencesActivity extends SherlockPreferenceActivity {
MasterSecret masterSecret = (MasterSecret)getIntent().getParcelableExtra("master_secret");
if (masterSecret != null) {
Intent importIntent = ContactAccessor.getInstance().getIntentForContactSelection();
Intent importIntent = new Intent(Intent.ACTION_PICK);
importIntent.setType(ContactsContract.Contacts.CONTENT_TYPE);
startActivityForResult(importIntent, IMPORT_IDENTITY_ID);
} else {
Toast.makeText(ApplicationPreferencesActivity.this,

View File

@@ -292,11 +292,7 @@ public class ContactSelectionListFragment extends SherlockListFragment
if (!isChecked)
throw new AssertionError("We shouldn't be unchecking data that doesn't exist.");
existing = new ContactData();
existing.id = contactData.id;
existing.name = contactData.name;
existing.numbers = new LinkedList<NumberData>();
existing = new ContactData(contactData.id, contactData.name);
selectedContacts.put(existing.id, existing);
}

View File

@@ -182,13 +182,7 @@ public class ContactSelectionRecentFragment extends SherlockListFragment
else if (type == Calls.OUTGOING_TYPE || type == RedPhoneCallTypes.OUTGOING) callTypeIcon.setImageDrawable(getResources().getDrawable(R.drawable.ic_call_log_list_outgoing_call));
else if (type == Calls.MISSED_TYPE || type == RedPhoneCallTypes.MISSED) callTypeIcon.setImageDrawable(getResources().getDrawable(R.drawable.ic_call_log_list_missed_call));
this.contactData = new ContactData();
if (name != null)
this.contactData.name = name;
this.contactData.id = id;
this.contactData.numbers = new LinkedList<ContactAccessor.NumberData>();
this.contactData = new ContactData(id, name);
this.contactData.numbers.add(new NumberData(null, number));
if (selectedContacts.containsKey(id))

View File

@@ -73,6 +73,8 @@ import org.thoughtcrime.securesms.util.MemoryCleaner;
import ws.com.google.android.mms.MmsException;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
/**
* Activity for displaying a message thread, as well as
@@ -104,7 +106,7 @@ public class ConversationActivity extends SherlockFragmentActivity
private Recipients recipients;
private long threadId;
private boolean sendEncrypted;
private boolean isEncryptedConversation;
private CharacterCalculator characterCalculator = new CharacterCalculator();
@@ -186,7 +188,7 @@ public class ConversationActivity extends SherlockFragmentActivity
MenuInflater inflater = this.getSupportMenuInflater();
menu.clear();
if (isSingleConversation() && sendEncrypted)
if (isSingleConversation() && isEncryptedConversation)
{
if (isAuthenticatedSession()) {
inflater.inflate(R.menu.conversation_secure_verified, menu);
@@ -199,6 +201,8 @@ public class ConversationActivity extends SherlockFragmentActivity
if (isSingleConversation()) {
inflater.inflate(R.menu.conversation_callable, menu);
} else if (isGroupConversation()) {
inflater.inflate(R.menu.conversation_group_options, menu);
}
inflater.inflate(R.menu.conversation, menu);
@@ -217,6 +221,7 @@ public class ConversationActivity extends SherlockFragmentActivity
case R.id.menu_abort_session: handleAbortSecureSession(); return true;
case R.id.menu_verify_recipient: handleVerifyRecipient(); return true;
case R.id.menu_verify_session: handleVerifySession(); return true;
case R.id.menu_group_recipients: handleDisplayGroupRecipients(); return true;
case android.R.id.home: finish(); return true;
}
@@ -225,7 +230,7 @@ public class ConversationActivity extends SherlockFragmentActivity
@Override
public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
if (sendEncrypted) {
if (isEncryptedConversation) {
android.view.MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.conversation_button_context, menu);
}
@@ -234,7 +239,7 @@ public class ConversationActivity extends SherlockFragmentActivity
@Override
public boolean onContextItemSelected(android.view.MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_context_send_unencrypted: sendMessage(false); return true;
case R.id.menu_context_send_unencrypted: sendMessage(true); return true;
}
return false;
@@ -305,6 +310,22 @@ public class ConversationActivity extends SherlockFragmentActivity
startActivity(dialIntent);
}
private void handleDisplayGroupRecipients() {
List<String> recipientStrings = new LinkedList<String>();
for (Recipient recipient : getRecipients().getRecipientsList()) {
recipientStrings.add(recipient.getName());
}
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.ConversationActivity_group_conversation_recipients);
builder.setIcon(R.drawable.ic_groups_holo_dark);
builder.setCancelable(true);
builder.setItems(recipientStrings.toArray(new String[]{}), null);
builder.setPositiveButton(android.R.string.ok, null);
builder.show();
}
private void handleDeleteThread() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.ConversationActivity_delete_thread_confirmation);
@@ -341,7 +362,7 @@ public class ConversationActivity extends SherlockFragmentActivity
if (isSingleConversation()) {
if (sendEncrypted) {
if (isEncryptedConversation) {
title = AuthenticityCalculator.getAuthenticatedName(this,
getRecipients().getPrimaryRecipient(),
masterSecret);
@@ -356,6 +377,10 @@ public class ConversationActivity extends SherlockFragmentActivity
} else {
subtitle = getRecipients().getPrimaryRecipient().getNumber();
}
} else if (isGroupConversation()) {
title = getString(R.string.ConversationActivity_group_conversation);
subtitle = String.format(getString(R.string.ConversationActivity_d_recipients_in_group),
getRecipients().getRecipientsList().size());
} else {
title = getString(R.string.ConversationActivity_compose_message);
subtitle = "";
@@ -375,12 +400,12 @@ public class ConversationActivity extends SherlockFragmentActivity
{
sendButton.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_menu_lock_holo_light, 0);
sendButton.setCompoundDrawablePadding(15);
this.sendEncrypted = true;
this.characterCalculator = new EncryptedCharacterCalculator();
this.isEncryptedConversation = true;
this.characterCalculator = new EncryptedCharacterCalculator();
} else {
sendButton.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
this.sendEncrypted = false;
this.characterCalculator = new CharacterCalculator();
this.isEncryptedConversation = false;
this.characterCalculator = new CharacterCalculator();
}
calculateCharactersRemaining();
@@ -411,7 +436,7 @@ public class ConversationActivity extends SherlockFragmentActivity
registerForContextMenu(sendButton);
if (getIntent().getStringExtra("forwarded_message") != null)
composeText.setText("FWD: " + getIntent().getStringExtra("forwarded_message"));
composeText.setText(R.string.ConversationActivity_forward_message_prefix+": " + getIntent().getStringExtra("forwarded_message"));
}
private void initializeRecipientsInput() {
@@ -526,6 +551,10 @@ public class ConversationActivity extends SherlockFragmentActivity
return getRecipients() != null && getRecipients().isSingleRecipient();
}
private boolean isGroupConversation() {
return getRecipients() != null && !getRecipients().isSingleRecipient();
}
private boolean isAuthenticatedSession() {
return AuthenticityCalculator.isAuthenticated(this,
getRecipients().getPrimaryRecipient(),
@@ -548,7 +577,7 @@ public class ConversationActivity extends SherlockFragmentActivity
if (rawText.length() < 1 && !attachmentManager.isAttachmentPresent())
throw new InvalidMessageException(getString(R.string.ConversationActivity_message_is_empty_exclamation));
if (!sendEncrypted && Tag.isTaggable(this, rawText))
if (!isEncryptedConversation && Tag.isTaggable(this, rawText))
rawText = Tag.getTaggedMessage(rawText);
return rawText;
@@ -575,7 +604,7 @@ public class ConversationActivity extends SherlockFragmentActivity
}
}
private void sendMessage(boolean sendEncrypted) {
private void sendMessage(boolean forcePlaintext) {
try {
Recipients recipients = getRecipients();
@@ -588,13 +617,13 @@ public class ConversationActivity extends SherlockFragmentActivity
if (attachmentManager.isAttachmentPresent()) {
allocatedThreadId = MessageSender.sendMms(ConversationActivity.this, masterSecret, recipients,
threadId, attachmentManager.getSlideDeck(), message,
sendEncrypted);
forcePlaintext);
} else if (recipients.isEmailRecipient()) {
allocatedThreadId = MessageSender.sendMms(ConversationActivity.this, masterSecret, recipients,
threadId, new SlideDeck(), message, sendEncrypted);
threadId, new SlideDeck(), message, forcePlaintext);
} else {
allocatedThreadId = MessageSender.send(ConversationActivity.this, masterSecret, recipients,
threadId, message, sendEncrypted);
threadId, message, forcePlaintext);
}
sendComplete(recipients, allocatedThreadId);
@@ -650,7 +679,7 @@ public class ConversationActivity extends SherlockFragmentActivity
private class SendButtonListener implements OnClickListener, TextView.OnEditorActionListener {
public void onClick(View v) {
sendMessage(sendEncrypted);
sendMessage(false);
}
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {

View File

@@ -30,17 +30,26 @@ import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MessageDisplayHelper;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessageRecord;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.mms.MmsFactory;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord.GroupData;
import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.service.MessageNotifier;
import org.thoughtcrime.securesms.util.InvalidMessageException;
import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.MultimediaMessagePdu;
import ws.com.google.android.mms.pdu.NotificationInd;
import ws.com.google.android.mms.pdu.PduHeaders;
import java.util.LinkedHashMap;
@@ -68,7 +77,9 @@ public class ConversationAdapter extends CursorAdapter {
private boolean dataChanged;
public ConversationAdapter(Recipients recipients, long threadId, Context context, MasterSecret masterSecret, Handler failedIconClickHandler) {
public ConversationAdapter(Recipients recipients, long threadId, Context context,
MasterSecret masterSecret, Handler failedIconClickHandler)
{
super(context, null);
this.context = context;
this.recipients = recipients;
@@ -85,27 +96,15 @@ public class ConversationAdapter extends CursorAdapter {
MessageNotifier.updateNotification(context, false);
}
private Recipient buildRecipient(String address) {
Recipient recipient;
try {
if (address == null) recipient = recipients.getPrimaryRecipient();
else recipient = RecipientFactory.getRecipientsFromString(context, address).getPrimaryRecipient();
} catch (RecipientFormattingException e) {
Log.w("ConversationAdapter", e);
recipient = new Recipient("Unknown", "Unknown", null);
}
return recipient;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
ConversationItem item = (ConversationItem)view;
long id = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.ID));
String type = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.TRANSPORT));
String type = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsDatabase.TRANSPORT));
MessageRecord messageRecord = getMessageRecord(id, cursor, type);
((ConversationItem)view).set(masterSecret, messageRecord, failedIconClickHandler);
item.set(masterSecret, messageRecord, failedIconClickHandler);
view.setOnTouchListener(touchListener);
}
@@ -135,35 +134,107 @@ public class ConversationAdapter extends CursorAdapter {
private int getItemViewType(Cursor cursor) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.ID));
String type = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.TRANSPORT));
String type = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsDatabase.TRANSPORT));
MessageRecord messageRecord = getMessageRecord(id, cursor, type);
if (messageRecord.isOutgoing()) return 0;
else return 1;
}
private MessageRecord getNewMmsMessageRecord(long messageId, Cursor cursor) {
MessageRecord messageRecord = getNewSmsMessageRecord(messageId, cursor);
long mmsType = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_TYPE));
long mmsBox = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX));
private MediaMmsMessageRecord getMediaMmsMessageRecord(long messageId, Cursor cursor) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.ID));
long date = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.DATE));
long box = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_BOX));
Recipient recipient = getIndividualRecipientFor(null);
GroupData groupData = null;
SlideDeck slideDeck;
try {
return MmsFactory.getMms(context, masterSecret, messageRecord, mmsType, mmsBox);
MultimediaMessagePdu pdu = DatabaseFactory.getEncryptingMmsDatabase(context, masterSecret).getMediaMessage(messageId);
slideDeck = new SlideDeck(context, masterSecret, pdu.getBody());
if (recipients != null && !recipients.isSingleRecipient()) {
int groupSize = pdu.getTo().length;
int groupSent = MmsDatabase.Types.isFailedMmsBox(box) ? 0 : groupSize;
int groupSendFailed = groupSize - groupSent;
if (groupSize <= 1) {
groupSize = cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsDatabase.GROUP_SIZE));
groupSent = cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsDatabase.MMS_GROUP_SENT_COUNT));
groupSendFailed = cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsDatabase.MMS_GROUP_SEND_FAILED_COUNT));
}
Log.w("ConversationAdapter", "MMS GroupSize: " + groupSize + " , GroupSent: " + groupSent + " , GroupSendFailed: " + groupSendFailed);
groupData = new MessageRecord.GroupData(groupSize, groupSent, groupSendFailed);
}
} catch (MmsException me) {
Log.w("ConversationAdapter", me);
return messageRecord;
slideDeck = null;
}
return new MediaMmsMessageRecord(context, id, recipients, recipient,
date, threadId, slideDeck, box, groupData);
}
private MessageRecord getNewSmsMessageRecord(long messageId, Cursor cursor) {
long date = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.DATE));
long type = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.TYPE));
String address = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS));
Recipient recipient = buildRecipient(address);
MessageRecord messageRecord = new MessageRecord(messageId, recipients, date, type, threadId);
private NotificationMmsMessageRecord getNotificationMmsMessageRecord(long messageId, Cursor cursor) {
Recipient recipient = getIndividualRecipientFor(null);
long id = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.ID));
long date = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.DATE));
messageRecord.setMessageRecipient(recipient);
setBody(cursor, messageRecord);
NotificationInd notification;
try {
notification = DatabaseFactory.getMmsDatabase(context).getNotificationMessage(messageId);
} catch (MmsException me) {
Log.w("ConversationAdapter", me);
notification = new NotificationInd(new PduHeaders());
}
return new NotificationMmsMessageRecord(id, recipients, recipient, date, threadId,
notification.getContentLocation(),
notification.getMessageSize(),
notification.getExpiry(),
notification.getStatus(),
notification.getTransactionId());
}
private SmsMessageRecord getSmsMessageRecord(long messageId, Cursor cursor) {
long date = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.DATE));
long type = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.TYPE));
String body = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.BODY));
String address = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS));
Recipient recipient = getIndividualRecipientFor(address);
MessageRecord.GroupData groupData = null;
if (recipients != null && !recipients.isSingleRecipient()) {
int groupSize = cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsDatabase.GROUP_SIZE));
int groupSent = cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsDatabase.SMS_GROUP_SENT_COUNT));
int groupSendFailed = cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsDatabase.SMS_GROUP_SEND_FAILED_COUNT));
Log.w("ConversationAdapter", "GroupSize: " + groupSize + " , GroupSent: " + groupSent + " , GroupSendFailed: " + groupSendFailed);
groupData = new MessageRecord.GroupData(groupSize, groupSent, groupSendFailed);
}
SmsMessageRecord messageRecord = new SmsMessageRecord(context, messageId, recipients,
recipient, date, type, threadId,
groupData);
if (body == null) {
body = "";
}
try {
String decryptedBody = MessageDisplayHelper.getDecryptedMessageBody(masterCipher, body);
messageRecord.setBody(decryptedBody);
} catch (InvalidMessageException ime) {
Log.w("ConversationAdapter", ime);
messageRecord.setBody(context.getString(R.string.MessageDisplayHelper_decryption_error_local_message_corrupted_mac_doesn_t_match_potential_tampering_question));
messageRecord.setEmphasis(true);
}
return messageRecord;
}
@@ -174,22 +245,35 @@ public class ConversationAdapter extends CursorAdapter {
MessageRecord messageRecord;
if (type.equals("mms")) messageRecord = getNewMmsMessageRecord(messageId, cursor);
else messageRecord = getNewSmsMessageRecord(messageId, cursor);
if (type.equals("mms")) {
long mmsType = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_TYPE));
if (mmsType == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND) {
messageRecord = getNotificationMmsMessageRecord(messageId, cursor);
} else {
messageRecord = getMediaMmsMessageRecord(messageId, cursor);
}
} else {
messageRecord = getSmsMessageRecord(messageId, cursor);
}
messageRecordCache.put(type + messageId, messageRecord);
return messageRecord;
}
protected void setBody(Cursor cursor, MessageRecord message) {
String body = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.BODY));
private Recipient getIndividualRecipientFor(String address) {
Recipient recipient;
if (body == null)
message.setBody("");
else
MessageDisplayHelper.setDecryptedMessageBody(context, body, message, masterCipher);
try {
if (address == null) recipient = recipients.getPrimaryRecipient();
else recipient = RecipientFactory.getRecipientsFromString(context, address).getPrimaryRecipient();
} catch (RecipientFormattingException e) {
Log.w("ConversationAdapter", e);
recipient = new Recipient("Unknown", "Unknown", null);
}
return recipient;
}
@Override
protected void onContentChanged() {
super.onContentChanged();

View File

@@ -21,8 +21,8 @@ import com.actionbarsherlock.app.SherlockListFragment;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessageRecord;
import org.thoughtcrime.securesms.database.loaders.ConversationLoader;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.recipients.Recipients;
import java.sql.Date;
@@ -98,9 +98,8 @@ public class ConversationFragment extends SherlockListFragment
clipboard.setText(body);
}
private void handleDeleteMessage(MessageRecord message) {
private void handleDeleteMessage(final MessageRecord message) {
final long messageId = message.getId();
final String transport = message.isMms() ? "mms" : "sms";
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.ConversationFragment_confirm_message_delete);
@@ -111,7 +110,7 @@ public class ConversationFragment extends SherlockListFragment
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (transport.equals("mms")) {
if (message.isMms()) {
DatabaseFactory.getMmsDatabase(getActivity()).delete(messageId);
} else {
DatabaseFactory.getSmsDatabase(getActivity()).deleteMessage(messageId);
@@ -165,7 +164,8 @@ public class ConversationFragment extends SherlockListFragment
@Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
return new ConversationLoader(getActivity(), threadId);
return new ConversationLoader(getActivity(), threadId,
(recipients != null && !recipients.isSingleRecipient()));
}
@Override

View File

@@ -26,10 +26,8 @@ import android.net.Uri;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.provider.Contacts.Intents;
import android.provider.ContactsContract.QuickContact;
import android.telephony.TelephonyManager;
import android.text.Spannable;
import android.text.format.DateUtils;
import android.text.style.ForegroundColorSpan;
@@ -43,16 +41,18 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import org.thoughtcrime.securesms.contacts.ContactIdentityManager;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.MessageRecord;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.MmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord.GroupData;
import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.protocol.Tag;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.service.SendReceiveService;
import java.io.File;
@@ -79,6 +79,7 @@ public class ConversationItem extends LinearLayout {
private TextView bodyText;
private TextView dateText;
private TextView groupStatusText;
private ImageView secureImage;
private ImageView failedImage;
private ImageView keyImage;
@@ -109,6 +110,7 @@ public class ConversationItem extends LinearLayout {
this.bodyText = (TextView) findViewById(R.id.conversation_item_body);
this.dateText = (TextView) findViewById(R.id.conversation_item_date);
this.groupStatusText = (TextView) findViewById(R.id.group_message_status);
this.secureImage = (ImageView)findViewById(R.id.sms_secure_indicator);
this.failedImage = (ImageView)findViewById(R.id.sms_failed_indicator);
this.keyImage = (ImageView)findViewById(R.id.key_exchange_indicator);
@@ -128,90 +130,28 @@ public class ConversationItem extends LinearLayout {
this.masterSecret = masterSecret;
this.failedIconHandler = failedIconHandler;
// Double-dispatch back to methods below.
messageRecord.setOnConversationItem(this);
setBodyText(messageRecord);
setStatusIcons(messageRecord);
setContactPhoto(messageRecord);
setGroupMessageStatus(messageRecord);
setEvents(messageRecord);
if (messageRecord instanceof NotificationMmsMessageRecord) {
setNotificationMmsAttributes((NotificationMmsMessageRecord)messageRecord);
} else if (messageRecord instanceof MediaMmsMessageRecord) {
setMediaMmsAttributes((MediaMmsMessageRecord)messageRecord);
}
}
public MessageRecord getMessageRecord() {
return messageRecord;
}
public void setMessageRecord(MessageRecord messageRecord) {
setBody(messageRecord);
setStatusIcons(messageRecord);
setEvents(messageRecord);
}
public void setMessageRecord(MmsMessageRecord messageRecord) {
setMessageRecord((MessageRecord)messageRecord);
if (messageRecord.isNotification())
setMmsNotificationAttributes(messageRecord);
else
setMmsMediaAttributes(messageRecord);
}
private void setMmsNotificationAttributes(MmsMessageRecord messageRecord) {
String messageSize = String.format(getContext()
.getString(R.string.ConversationItem_message_size_d_kb),
messageRecord.getMessageSize());
String expires = String.format(getContext()
.getString(R.string.ConversationItem_expires_s),
DateUtils.getRelativeTimeSpanString(getContext(),
messageRecord.getExpiration(),
false));
dateText.setText(messageSize + "\n" + expires);
if (MmsDatabase.Types.isDisplayDownloadButton(messageRecord.getStatus())) {
mmsDownloadButton.setVisibility(View.VISIBLE);
mmsDownloadingLabel.setVisibility(View.GONE);
} else {
mmsDownloadingLabel.setText(MmsDatabase.Types.getLabelForStatus(context, messageRecord.getStatus()));
mmsDownloadButton.setVisibility(View.GONE);
mmsDownloadingLabel.setVisibility(View.VISIBLE);
}
if (MmsDatabase.Types.isHardError(messageRecord.getStatus()))
failedImage.setVisibility(View.VISIBLE);
}
private void setMmsMediaAttributes(MmsMessageRecord messageRecord) {
SlideDeck slideDeck = messageRecord.getSlideDeck();
List<Slide> slides = slideDeck.getSlides();
Iterator<Slide> iterator = slides.iterator();
while (iterator.hasNext()) {
Slide slide = iterator.next();
if (slide.hasImage()) {
mmsThumbnail.setImageBitmap(slide.getThumbnail());
mmsThumbnail.setOnClickListener(new ThumbnailClickListener(slide));
mmsThumbnail.setOnLongClickListener(new ThumbnailSaveListener(slide));
mmsThumbnail.setVisibility(View.VISIBLE);
return;
}
}
mmsThumbnail.setVisibility(View.GONE);
}
public void setHandler(Handler failedIconHandler) {
this.failedIconHandler = failedIconHandler;
}
private void checkForAutoInitiate(MessageRecord messageRecord) {
if (AutoInitiateActivity.isValidAutoInitiateSituation(context, masterSecret, messageRecord.getRecipients().getPrimaryRecipient(), messageRecord.getBody(), messageRecord.getThreadId())) {
AutoInitiateActivity.exemptThread(context, messageRecord.getThreadId());
Intent intent = new Intent();
intent.setClass(context, AutoInitiateActivity.class);
intent.putExtra("threadId", messageRecord.getThreadId());
intent.putExtra("masterSecret", masterSecret);
intent.putExtra("recipient", messageRecord.getRecipients().getPrimaryRecipient());
context.startActivity(intent);
}
}
/// MessageRecord Attribute Parsers
private void setBodyText(MessageRecord messageRecord) {
String body = messageRecord.getBody();
@@ -230,63 +170,12 @@ public class ConversationItem extends LinearLayout {
}
}
private void setContactPhotoForUserIdentity() {
String configuredContact = PreferenceManager.getDefaultSharedPreferences(context).getString(ApplicationPreferencesActivity.IDENTITY_PREF, null);
try {
if (configuredContact != null) {
Recipient recipient = RecipientFactory.getRecipientForUri(context, Uri.parse(configuredContact));
if (recipient != null) {
contactPhoto.setImageBitmap(recipient.getContactPhoto());
return;
}
}
if (hasLocalNumber()) {
contactPhoto.setImageBitmap(RecipientFactory.getRecipientsFromString(context, getLocalNumber()).getPrimaryRecipient().getContactPhoto());
} else {
contactPhoto.setImageDrawable(context.getResources().getDrawable(R.drawable.ic_contact_picture));
}
} catch (RecipientFormattingException rfe) {
Log.w("ConversationItem", rfe);
}
}
private void setBodyImage(MessageRecord messageRecord) {
final Recipient recipient = messageRecord.getMessageRecipient();
if (!messageRecord.isOutgoing()) {
contactPhoto.setImageBitmap(recipient.getContactPhoto());
contactPhoto.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (recipient.getContactUri() != null) {
QuickContact.showQuickContact(context, contactPhoto, recipient.getContactUri(), QuickContact.MODE_LARGE, null);
} else {
Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, Uri.fromParts("tel", recipient.getNumber(), null));
context.startActivity(intent);
}
}
});
} else {
private void setContactPhoto(MessageRecord messageRecord) {
if (messageRecord.isOutgoing()) {
setContactPhotoForUserIdentity();
} else {
setContactPhotoForRecipient(messageRecord.getIndividualRecipient());
}
contactPhoto.setVisibility(View.VISIBLE);
}
private void setBody(MessageRecord messageRecord) {
setBodyText(messageRecord);
setBodyImage(messageRecord);
}
private String getLocalNumber() {
return ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getLine1Number();
}
private boolean hasLocalNumber() {
String number = getLocalNumber();
return (number != null) && (number.trim().length() > 0);
}
private void setStatusIcons(MessageRecord messageRecord) {
@@ -308,10 +197,128 @@ public class ConversationItem extends LinearLayout {
private void setEvents(MessageRecord messageRecord) {
setClickable(messageRecord.isKeyExchange() && !messageRecord.isOutgoing());
if (!messageRecord.isOutgoing() && messageRecord.getRecipients().isSingleRecipient())
checkForAutoInitiate(messageRecord);
if (!messageRecord.isOutgoing() && messageRecord.getRecipients().isSingleRecipient()) {
checkForAutoInitiate(messageRecord.getIndividualRecipient(),
messageRecord.getBody(),
messageRecord.getThreadId());
}
}
private void setGroupMessageStatus(MessageRecord messageRecord) {
GroupData groupData = messageRecord.getGroupData();
if (groupData != null) {
String status = String.format("Sent (%d/%d)", groupData.groupSentCount, groupData.groupSize);
if (groupData.groupSendFailedCount != 0)
status = status + String.format(", Failed (%d/%d)", groupData.groupSendFailedCount, groupData.groupSize);
this.groupStatusText.setText(status);
this.groupStatusText.setVisibility(View.VISIBLE);
} else {
this.groupStatusText.setVisibility(View.GONE);
}
}
private void setNotificationMmsAttributes(NotificationMmsMessageRecord messageRecord) {
String messageSize = String.format(getContext()
.getString(R.string.ConversationItem_message_size_d_kb),
messageRecord.getMessageSize());
String expires = String.format(getContext()
.getString(R.string.ConversationItem_expires_s),
DateUtils.getRelativeTimeSpanString(getContext(),
messageRecord.getExpiration(),
false));
dateText.setText(messageSize + "\n" + expires);
if (MmsDatabase.Types.isDisplayDownloadButton(messageRecord.getStatus())) {
mmsDownloadButton.setVisibility(View.VISIBLE);
mmsDownloadingLabel.setVisibility(View.GONE);
} else {
mmsDownloadingLabel.setText(MmsDatabase.Types.getLabelForStatus(context, messageRecord.getStatus()));
mmsDownloadButton.setVisibility(View.GONE);
mmsDownloadingLabel.setVisibility(View.VISIBLE);
}
}
private void setMediaMmsAttributes(MediaMmsMessageRecord messageRecord) {
SlideDeck slideDeck = messageRecord.getSlideDeck();
if (slideDeck != null) {
List<Slide> slides = slideDeck.getSlides();
Iterator<Slide> iterator = slides.iterator();
while (iterator.hasNext()) {
Slide slide = iterator.next();
if (slide.hasImage()) {
mmsThumbnail.setImageBitmap(slide.getThumbnail());
mmsThumbnail.setOnClickListener(new ThumbnailClickListener(slide));
mmsThumbnail.setOnLongClickListener(new ThumbnailSaveListener(slide));
mmsThumbnail.setVisibility(View.VISIBLE);
return;
}
}
}
mmsThumbnail.setVisibility(View.GONE);
}
/// Helper Methods
private void checkForAutoInitiate(Recipient recipient, String body, long threadId) {
if (AutoInitiateActivity.isValidAutoInitiateSituation(context, masterSecret, recipient,
body, threadId))
{
AutoInitiateActivity.exemptThread(context, threadId);
Intent intent = new Intent();
intent.setClass(context, AutoInitiateActivity.class);
intent.putExtra("threadId", threadId);
intent.putExtra("masterSecret", masterSecret);
intent.putExtra("recipient", recipient);
context.startActivity(intent);
}
}
private void setContactPhotoForUserIdentity() {
Uri selfIdentityContact = ContactIdentityManager.getInstance(context).getSelfIdentityUri();
if (selfIdentityContact!= null) {
Recipient recipient = RecipientFactory.getRecipientForUri(context, selfIdentityContact);
if (recipient != null) {
contactPhoto.setImageBitmap(recipient.getContactPhoto());
return;
}
} else {
contactPhoto.setImageDrawable(context.getResources().getDrawable(R.drawable.ic_contact_picture));
}
contactPhoto.setVisibility(View.VISIBLE);
}
private void setContactPhotoForRecipient(final Recipient recipient) {
contactPhoto.setImageBitmap(recipient.getContactPhoto());
contactPhoto.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (recipient.getContactUri() != null) {
QuickContact.showQuickContact(context, contactPhoto, recipient.getContactUri(), QuickContact.MODE_LARGE, null);
} else {
Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, Uri.fromParts("tel", recipient.getNumber(), null));
context.startActivity(intent);
}
}
});
contactPhoto.setVisibility(View.VISIBLE);
}
/// Event handlers
private void handleKeyExchangeClicked() {
Intent intent = new Intent(context, ReceiveKeyActivity.class);
intent.putExtra("recipient", messageRecord.getRecipients().getPrimaryRecipient());
@@ -471,15 +478,16 @@ public class ConversationItem extends LinearLayout {
private class MmsDownloadClickListener implements View.OnClickListener {
public void onClick(View v) {
Log.w("MmsDownloadClickListener", "Content location: " + new String(((MmsMessageRecord)messageRecord).getContentLocation()));
NotificationMmsMessageRecord notificationRecord = (NotificationMmsMessageRecord)messageRecord;
Log.w("MmsDownloadClickListener", "Content location: " + new String(notificationRecord.getContentLocation()));
mmsDownloadButton.setVisibility(View.GONE);
mmsDownloadingLabel.setVisibility(View.VISIBLE);
Intent intent = new Intent(context, SendReceiveService.class);
intent.putExtra("content_location", new String(((MmsMessageRecord)messageRecord).getContentLocation()));
intent.putExtra("message_id", ((MmsMessageRecord)messageRecord).getId());
intent.putExtra("transaction_id", ((MmsMessageRecord)messageRecord).getTransactionId());
intent.putExtra("thread_id", ((MmsMessageRecord)messageRecord).getThreadId());
intent.putExtra("content_location", new String(notificationRecord.getContentLocation()));
intent.putExtra("message_id", notificationRecord.getId());
intent.putExtra("transaction_id", notificationRecord.getTransactionId());
intent.putExtra("thread_id", notificationRecord.getThreadId());
intent.setAction(SendReceiveService.DOWNLOAD_MMS_ACTION);
context.startService(intent);
}

View File

@@ -12,6 +12,7 @@ import android.database.ContentObserver;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcelable;
import android.provider.ContactsContract;
import android.util.Log;
import com.actionbarsherlock.app.SherlockFragmentActivity;
@@ -20,7 +21,6 @@ import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;
import org.thoughtcrime.securesms.ApplicationExportManager.ApplicationExportListener;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.crypto.DecryptingQueue;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.MasterSecret;
@@ -249,7 +249,7 @@ public class ConversationListActivity extends SherlockFragmentActivity
}
};
getContentResolver().registerContentObserver(ContactAccessor.getInstance().getContactsUri(),
getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI,
true, observer);
}

View File

@@ -23,9 +23,8 @@ import android.view.ViewGroup;
import android.widget.CursorAdapter;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessageRecord;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.protocol.Prefix;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
@@ -68,28 +67,19 @@ public class ConversationListAdapter extends CursorAdapter {
long count = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.MESSAGE_COUNT));
long read = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.READ));
MessageRecord message = new MessageRecord(-1, recipients, date, count, read == 1, threadId);
setBody(cursor, message);
ThreadRecord thread = new ThreadRecord(context, recipients, date, count, read == 1, threadId);
setBody(cursor, thread);
((ConversationListItem)view).set(message, batchMode);
((ConversationListItem)view).set(thread, batchMode);
}
protected void filterBody(MessageRecord message, String body) {
protected void filterBody(ThreadRecord thread, String body) {
if (body == null) body = "(No subject)";
if (body.startsWith(Prefix.SYMMETRIC_ENCRYPT) || body.startsWith(Prefix.ASYMMETRIC_ENCRYPT) || body.startsWith(Prefix.ASYMMETRIC_LOCAL_ENCRYPT)) {
message.setBody(context.getString(R.string.ConversationListAdapter_encrypted_message_enter_passphrase));
message.setEmphasis(true);
} else if (body.startsWith(Prefix.KEY_EXCHANGE)) {
message.setBody(context.getString(R.string.ConversationListAdapter_key_exchange_message));
message.setEmphasis(true);
} else {
message.setBody(body);
}
thread.setBody(body);
}
protected void setBody(Cursor cursor, MessageRecord message) {
filterBody(message, cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET)));
protected void setBody(Cursor cursor, ThreadRecord thread) {
filterBody(thread, cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET)));
}
public void addToBatchSet(long threadId) {

View File

@@ -33,7 +33,7 @@ import android.widget.QuickContactBadge;
import android.widget.RelativeLayout;
import android.widget.TextView;
import org.thoughtcrime.securesms.database.MessageRecord;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.recipients.Recipients;
import java.util.Set;
@@ -84,22 +84,22 @@ public class ConversationListItem extends RelativeLayout {
super(context, attrs);
}
public void set(MessageRecord message, boolean batchMode) {
this.recipients = message.getRecipients();
this.threadId = message.getThreadId();
this.fromView.setText(formatFrom(recipients, message.getCount(), message.getRead()));
public void set(ThreadRecord thread, boolean batchMode) {
this.recipients = thread.getRecipients();
this.threadId = thread.getThreadId();
this.fromView.setText(formatFrom(recipients, thread.getCount(), thread.isRead()));
if (message.isKeyExchange())
if (thread.isKeyExchange())
this.subjectView.setText(R.string.ConversationListItem_key_exchange_message,
TextView.BufferType.SPANNABLE);
else
this.subjectView.setText(message.getBody(), TextView.BufferType.SPANNABLE);
this.subjectView.setText(thread.getBody(), TextView.BufferType.SPANNABLE);
if (message.getEmphasis())
if (thread.getEmphasis())
((Spannable)this.subjectView.getText()).setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), 0, this.subjectView.getText().length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
if (message.getDate() > 0)
this.dateView.setText(DateUtils.getRelativeTimeSpanString(getContext(), message.getDate(), false));
if (thread.getDate() > 0)
this.dateView.setText(DateUtils.getRelativeTimeSpanString(getContext(), thread.getDate(), false));
if (selectedThreads != null)
this.checkbox.setChecked(selectedThreads.contains(threadId));

View File

@@ -22,8 +22,9 @@ import android.database.Cursor;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MessageDisplayHelper;
import org.thoughtcrime.securesms.database.MessageRecord;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.util.InvalidMessageException;
/**
* A ConversationListAdapter that decrypts encrypted message bodies.
@@ -43,9 +44,16 @@ public class DecryptingConversationListAdapter extends ConversationListAdapter {
}
@Override
protected void setBody(Cursor cursor, MessageRecord message) {
protected void setBody(Cursor cursor, ThreadRecord thread) {
String body = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET));
if (body == null || body.equals("")) body = "(No subject)";
MessageDisplayHelper.setDecryptedMessageBody(context, body, message, bodyCipher);
try {
String decryptedBody = MessageDisplayHelper.getDecryptedMessageBody(bodyCipher, body);
thread.setBody(decryptedBody);
} catch (InvalidMessageException ime) {
thread.setBody(context.getString(R.string.MessageDisplayHelper_decryption_error_local_message_corrupted_mac_doesn_t_match_potential_tampering_question));
thread.setEmphasis(true);
}
}
}

View File

@@ -36,14 +36,15 @@ import org.thoughtcrime.securesms.util.MemoryCleaner;
*
* @author Moxie Marlinspike
*/
public class PassphraseCreateActivity extends PassphraseActivity {
private EditText passphraseEdit;
private EditText passphraseRepeatEdit;
private Button okButton;
private Button cancelButton;
private EditText passphraseEdit;
private EditText passphraseRepeatEdit;
private Button okButton;
private Button cancelButton;
public PassphraseCreateActivity() { }
public PassphraseCreateActivity() { }
@Override
public void onCreate(Bundle savedInstanceState) {

View File

@@ -177,11 +177,11 @@ public class ReviewIdentitiesActivity extends SherlockListActivity {
boolean valid;
String identityKeyString = cursor.getString(cursor.getColumnIndexOrThrow(IdentityDatabase.IDENTITY_KEY));
String identityName = cursor.getString(cursor.getColumnIndexOrThrow(IdentityDatabase.IDENTITY_NAME));
String identityName = cursor.getString(cursor.getColumnIndexOrThrow(IdentityDatabase.IDENTITY_NAME));
try {
String mac = cursor.getString(cursor.getColumnIndexOrThrow(IdentityDatabase.MAC));
valid = masterCipher.verifyMacFor(identityName + identityKeyString, Base64.decode(mac));
valid = masterCipher.verifyMacFor(identityName + identityKeyString, Base64.decode(mac));
identityKey = new IdentityKey(Base64.decode(identityKeyString), 0);
} catch (InvalidKeyException ike) {
Log.w("ReviewIdentitiesActivity",ike);

View File

@@ -17,56 +17,257 @@
package org.thoughtcrime.securesms.contacts;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.MergeCursor;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.PhoneLookup;
import android.support.v4.content.CursorLoader;
import android.telephony.PhoneNumberUtils;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.IdentityKey;
import org.thoughtcrime.securesms.crypto.InvalidKeyException;
import org.thoughtcrime.securesms.util.Base64;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/**
* Android changed their contacts API pretty heavily between
* 1.x and 2.x. This class provides a common interface to both
* API operations, using a singleton pattern that will Class.forName
* the correct one so we don't trigger NoClassDefFound exceptions on
* old platforms.
* This class was originally a layer of indirection between
* ContactAccessorNewApi and ContactAccesorOldApi, which corresponded
* to the API changes between 1.x and 2.x.
*
* Now that we no longer support 1.x, this class mostly serves as a place
* to encapsulate Contact-related logic. It's still a singleton, mostly
* just because that's how it's currently called from everywhere.
*
* @author Moxie Marlinspike
*/
public abstract class ContactAccessor {
public static final int UNIQUE_ID = 0;
public static final int DISPLAY_NAME = 1;
public class ContactAccessor {
private static final ContactAccessor sInstance = new ContactAccessorNewApi();
private static final ContactAccessor instance = new ContactAccessor();
public static synchronized ContactAccessor getInstance() {
return sInstance;
return instance;
}
public abstract NameAndNumber getNameAndNumberFromContact(Context context, Uri uri);
public abstract String getNameFromContact(Context context, Uri uri);
public abstract IdentityKey importIdentityKey(Context context, Uri uri);
public abstract void insertIdentityKey(Context context, Uri uri, IdentityKey identityKey);
public abstract Intent getIntentForContactSelection();
public abstract List<String> getNumbersForThreadSearchFilter(String constraint, ContentResolver contentResolver);
public abstract List<ContactData> getGroupMembership(Context context, long groupId);
public abstract Cursor getCursorForContactGroups(Context context);
public abstract CursorLoader getCursorLoaderForContactGroups(Context context);
public abstract CursorLoader getCursorLoaderForContactsWithNumbers(Context context);
public abstract Cursor getCursorForContactsWithNumbers(Context context);
public abstract GroupData getGroupData(Context context, Cursor cursor);
public abstract ContactData getContactData(Context context, Cursor cursor);
public abstract Cursor getCursorForRecipientFilter(CharSequence constraint, ContentResolver mContentResolver);
public abstract CharSequence phoneTypeToString(Context mContext, int type, CharSequence label);
public abstract String getNameForNumber(Context context, String number);
public abstract Uri getContactsUri();
public CursorLoader getCursorLoaderForContactsWithNumbers(Context context) {
Uri uri = ContactsContract.Contacts.CONTENT_URI;
String selection = ContactsContract.Contacts.HAS_PHONE_NUMBER + " = 1";
return new CursorLoader(context, uri, null, selection, null,
ContactsContract.Contacts.DISPLAY_NAME + " ASC");
}
public CursorLoader getCursorLoaderForContactGroups(Context context) {
return new CursorLoader(context, ContactsContract.Groups.CONTENT_URI,
null, null, null, ContactsContract.Groups.TITLE + " ASC");
}
public Cursor getCursorForContactsWithNumbers(Context context) {
Uri uri = ContactsContract.Contacts.CONTENT_URI;
String selection = ContactsContract.Contacts.HAS_PHONE_NUMBER + " = 1";
return context.getContentResolver().query(uri, null, selection, null,
ContactsContract.Contacts.DISPLAY_NAME + " ASC");
}
public String getNameFromContact(Context context, Uri uri) {
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(uri, new String[] {Contacts.DISPLAY_NAME},
null, null, null);
if (cursor != null && cursor.moveToFirst())
return cursor.getString(0);
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
public String getNameForNumber(Context context, String number) {
Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
try {
if (cursor != null && cursor.moveToFirst())
return cursor.getString(cursor.getColumnIndexOrThrow(PhoneLookup.DISPLAY_NAME));
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
public GroupData getGroupData(Context context, Cursor cursor) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ContactsContract.Groups._ID));
String title = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Groups.TITLE));
return new GroupData(id, title);
}
public ContactData getContactData(Context context, Cursor cursor) {
return getContactData(context,
cursor.getString(cursor.getColumnIndexOrThrow(Contacts.DISPLAY_NAME)),
cursor.getLong(cursor.getColumnIndexOrThrow(Contacts._ID)));
}
private ContactData getContactData(Context context, String displayName, long id) {
ContactData contactData = new ContactData(id, displayName);
Cursor numberCursor = null;
try {
numberCursor = context.getContentResolver().query(Phone.CONTENT_URI, null,
Phone.CONTACT_ID + " = ?",
new String[] {contactData.id + ""}, null);
while (numberCursor != null && numberCursor.moveToNext()) {
int type = numberCursor.getInt(numberCursor.getColumnIndexOrThrow(Phone.TYPE));
String label = numberCursor.getString(numberCursor.getColumnIndexOrThrow(Phone.LABEL));
String number = numberCursor.getString(numberCursor.getColumnIndexOrThrow(Phone.NUMBER));
String typeLabel = Phone.getTypeLabel(context.getResources(), type, label).toString();
contactData.numbers.add(new NumberData(typeLabel, number));
}
} finally {
if (numberCursor != null)
numberCursor.close();
}
return contactData;
}
public List<ContactData> getGroupMembership(Context context, long groupId) {
LinkedList<ContactData> contacts = new LinkedList<ContactData>();
Cursor groupMembership = null;
try {
String selection = ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID + " = ? AND " +
ContactsContract.CommonDataKinds.GroupMembership.MIMETYPE + " = ?";
String[] args = new String[] {groupId+"",
ContactsContract.CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE};
groupMembership = context.getContentResolver().query(Data.CONTENT_URI, null, selection, args, null);
while (groupMembership != null && groupMembership.moveToNext()) {
String displayName = groupMembership.getString(groupMembership.getColumnIndexOrThrow(Data.DISPLAY_NAME));
long contactId = groupMembership.getLong(groupMembership.getColumnIndexOrThrow(Data.CONTACT_ID));
contacts.add(getContactData(context, displayName, contactId));
}
} finally {
if (groupMembership != null)
groupMembership.close();
}
return contacts;
}
public List<String> getNumbersForThreadSearchFilter(String constraint, ContentResolver contentResolver) {
LinkedList<String> numberList = new LinkedList<String>();
Cursor cursor = null;
try {
cursor = contentResolver.query(Uri.withAppendedPath(Phone.CONTENT_FILTER_URI,
Uri.encode(constraint)),
null, null, null, null);
while (cursor != null && cursor.moveToNext()) {
numberList.add(cursor.getString(cursor.getColumnIndexOrThrow(Phone.NUMBER)));
}
} finally {
if (cursor != null)
cursor.close();
}
return numberList;
}
public CharSequence phoneTypeToString(Context mContext, int type, CharSequence label) {
return Phone.getTypeLabel(mContext.getResources(), type, label);
}
public void insertIdentityKey(Context context, List<Long> rawContactIds, IdentityKey identityKey) {
for (long rawContactId : rawContactIds) {
Log.w("ContactAccessorNewApi", "Inserting data for raw contact id: " + rawContactId);
ContentValues contentValues = new ContentValues();
contentValues.put(Data.RAW_CONTACT_ID, rawContactId);
contentValues.put(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE);
contentValues.put(Im.PROTOCOL, Im.PROTOCOL_CUSTOM);
contentValues.put(Im.CUSTOM_PROTOCOL, "TextSecure-IdentityKey");
contentValues.put(Im.DATA, Base64.encodeBytes(identityKey.serialize()));
context.getContentResolver().insert(Data.CONTENT_URI, contentValues);
}
}
public IdentityKey importIdentityKey(Context context, Uri uri) {
long contactId = getContactIdFromLookupUri(context, uri);
String selection = Im.CONTACT_ID + " = ? AND " + Im.PROTOCOL + " = ? AND " + Im.CUSTOM_PROTOCOL + " = ?";
String[] selectionArgs = new String[] {contactId+"", Im.PROTOCOL_CUSTOM+"", "TextSecure-IdentityKey"};
Cursor cursor = context.getContentResolver().query(Data.CONTENT_URI, null, selection, selectionArgs, null);
try {
if (cursor != null && cursor.moveToFirst()) {
String data = cursor.getString(cursor.getColumnIndexOrThrow(Im.DATA));
if (data != null)
return new IdentityKey(Base64.decode(data), 0);
}
} catch (InvalidKeyException e) {
Log.w("ContactAccessorNewApi", e);
return null;
} catch (IOException e) {
Log.w("ContactAccessorNewApi", e);
return null;
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
private long getContactIdFromLookupUri(Context context, Uri uri) {
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(uri,
new String[] {ContactsContract.Contacts._ID},
null, null, null);
if (cursor != null && cursor.moveToFirst()) {
return cursor.getLong(0);
} else {
return -1;
}
} finally {
if (cursor != null)
cursor.close();
}
}
public static class NumberData implements Parcelable {
@@ -80,8 +281,8 @@ public abstract class ContactAccessor {
}
};
public String number;
public String type;
public final String number;
public final String type;
public NumberData(String type, String number) {
this.type = type;
@@ -104,8 +305,13 @@ public abstract class ContactAccessor {
}
public static class GroupData {
public long id;
public String name;
public final long id;
public final String name;
public GroupData(long id, String name) {
this.id = id;
this.name = name;
}
}
public static class ContactData implements Parcelable {
@@ -120,11 +326,15 @@ public abstract class ContactAccessor {
}
};
public long id;
public String name;
public List<NumberData> numbers;
public final long id;
public final String name;
public final List<NumberData> numbers;
public ContactData() {}
public ContactData(long id, String name) {
this.id = id;
this.name = name;
this.numbers = new LinkedList<NumberData>();
}
public ContactData(Parcel in) {
id = in.readLong();
@@ -144,4 +354,81 @@ public abstract class ContactAccessor {
}
}
/***
* If the code below looks shitty to you, that's because it was taken
* directly from the Android source, where shitty code is all you get.
*/
public Cursor getCursorForRecipientFilter(CharSequence constraint,
ContentResolver mContentResolver)
{
final String SORT_ORDER = Contacts.TIMES_CONTACTED + " DESC," +
Contacts.DISPLAY_NAME + "," + Phone.TYPE;
final String[] PROJECTION_PHONE = {
Phone._ID, // 0
Phone.CONTACT_ID, // 1
Phone.TYPE, // 2
Phone.NUMBER, // 3
Phone.LABEL, // 4
Phone.DISPLAY_NAME, // 5
};
String phone = "";
String cons = null;
if (constraint != null) {
cons = constraint.toString();
if (RecipientsAdapter.usefulAsDigits(cons)) {
phone = PhoneNumberUtils.convertKeypadLettersToDigits(cons);
if (phone.equals(cons)) {
phone = "";
} else {
phone = phone.trim();
}
}
}
Uri uri = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri.encode(cons));
String selection = String.format("%s=%s OR %s=%s OR %s=%s",
Phone.TYPE,
Phone.TYPE_MOBILE,
Phone.TYPE,
Phone.TYPE_WORK_MOBILE,
Phone.TYPE,
Phone.TYPE_MMS);
Cursor phoneCursor = mContentResolver.query(uri,
PROJECTION_PHONE,
null,
null,
SORT_ORDER);
if (phone.length() > 0) {
ArrayList result = new ArrayList();
result.add(Integer.valueOf(-1)); // ID
result.add(Long.valueOf(-1)); // CONTACT_ID
result.add(Integer.valueOf(Phone.TYPE_CUSTOM)); // TYPE
result.add(phone); // NUMBER
/*
* The "\u00A0" keeps Phone.getDisplayLabel() from deciding
* to display the default label ("Home") next to the transformation
* of the letters into numbers.
*/
result.add("\u00A0"); // LABEL
result.add(cons); // NAME
ArrayList<ArrayList> wrap = new ArrayList<ArrayList>();
wrap.add(result);
ArrayListCursor translated = new ArrayListCursor(PROJECTION_PHONE, wrap);
return new MergeCursor(new Cursor[] { translated, phoneCursor });
} else {
return phoneCursor;
}
}
}

View File

@@ -1,424 +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.contacts;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.MergeCursor;
import android.net.Uri;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.PhoneLookup;
import android.provider.ContactsContract.RawContacts;
import android.support.v4.content.CursorLoader;
import android.telephony.PhoneNumberUtils;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.IdentityKey;
import org.thoughtcrime.securesms.crypto.InvalidKeyException;
import org.thoughtcrime.securesms.util.Base64;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/**
* Interface into the Android 2.x+ contacts operations.
*
* @author Stuart Anderson
*/
public class ContactAccessorNewApi extends ContactAccessor {
private static final String SORT_ORDER = Contacts.TIMES_CONTACTED + " DESC," + Contacts.DISPLAY_NAME + "," + Phone.TYPE;
private static final String[] PROJECTION_PHONE = {
Phone._ID, // 0
Phone.CONTACT_ID, // 1
Phone.TYPE, // 2
Phone.NUMBER, // 3
Phone.LABEL, // 4
Phone.DISPLAY_NAME, // 5
};
@Override
public List<String> getNumbersForThreadSearchFilter(String constraint, ContentResolver contentResolver) {
LinkedList<String> numberList = new LinkedList<String>();
Cursor cursor = null;
try {
cursor = contentResolver.query(Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri.encode(constraint)),
null, null, null, null);
while (cursor != null && cursor.moveToNext())
numberList.add(cursor.getString(cursor.getColumnIndexOrThrow(Phone.NUMBER)));
} finally {
if (cursor != null)
cursor.close();
}
return numberList;
}
@Override
public Cursor getCursorForRecipientFilter(CharSequence constraint, ContentResolver mContentResolver) {
String phone = "";
String cons = null;
if (constraint != null) {
cons = constraint.toString();
if (RecipientsAdapter.usefulAsDigits(cons)) {
phone = PhoneNumberUtils.convertKeypadLettersToDigits(cons);
if (phone.equals(cons)) {
phone = "";
} else {
phone = phone.trim();
}
}
}
Uri uri = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri.encode(cons));
String selection = String.format("%s=%s OR %s=%s OR %s=%s",
Phone.TYPE,
Phone.TYPE_MOBILE,
Phone.TYPE,
Phone.TYPE_WORK_MOBILE,
Phone.TYPE,
Phone.TYPE_MMS);
Cursor phoneCursor = mContentResolver.query(uri,
PROJECTION_PHONE,
null,
null,
SORT_ORDER);
if (phone.length() > 0) {
ArrayList result = new ArrayList();
result.add(Integer.valueOf(-1)); // ID
result.add(Long.valueOf(-1)); // CONTACT_ID
result.add(Integer.valueOf(Phone.TYPE_CUSTOM)); // TYPE
result.add(phone); // NUMBER
/*
* The "\u00A0" keeps Phone.getDisplayLabel() from deciding
* to display the default label ("Home") next to the transformation
* of the letters into numbers.
*/
result.add("\u00A0"); // LABEL
result.add(cons); // NAME
ArrayList<ArrayList> wrap = new ArrayList<ArrayList>();
wrap.add(result);
ArrayListCursor translated = new ArrayListCursor(PROJECTION_PHONE, wrap);
return new MergeCursor(new Cursor[] { translated, phoneCursor });
} else {
return phoneCursor;
}
}
@Override
public CharSequence phoneTypeToString( Context mContext, int type, CharSequence label ) {
return Phone.getTypeLabel(mContext.getResources(), type, label);
}
@Override
public Intent getIntentForContactSelection() {
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType(ContactsContract.Contacts.CONTENT_TYPE);
return intent;
}
private long getContactIdFromLookupUri(Context context, Uri uri) {
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(uri, new String[] {ContactsContract.Contacts._ID}, null, null, null);
if (cursor != null && cursor.moveToFirst())
return cursor.getLong(0);
else
return -1;
} finally {
if (cursor != null)
cursor.close();
}
}
private ArrayList<Long> getRawContactIds(Context context, long contactId) {
Cursor cursor = null;
ArrayList<Long> rawContactIds = new ArrayList<Long>();
try {
cursor = context.getContentResolver().query(RawContacts.CONTENT_URI, new String[] {RawContacts._ID},
RawContacts.CONTACT_ID + " = ?", new String[] {contactId+""},
null);
if (cursor == null)
return rawContactIds;
while (cursor.moveToNext()) {
rawContactIds.add(Long.valueOf(cursor.getLong(0)));
}
} finally {
if (cursor != null)
cursor.close();
}
return rawContactIds;
}
@Override
public void insertIdentityKey(Context context, Uri uri, IdentityKey identityKey) {
long contactId = getContactIdFromLookupUri(context, uri);
Log.w("ContactAccessorNewApi", "Got contact ID: " + contactId + " from uri: " + uri.toString());
ArrayList<Long> rawContactIds = getRawContactIds(context, contactId);
for (long rawContactId : rawContactIds) {
Log.w("ContactAccessorNewApi", "Inserting data for raw contact id: " + rawContactId);
ContentValues contentValues = new ContentValues();
contentValues.put(Data.RAW_CONTACT_ID, rawContactId);
contentValues.put(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE);
contentValues.put(Im.PROTOCOL, Im.PROTOCOL_CUSTOM);
contentValues.put(Im.CUSTOM_PROTOCOL, "TextSecure-IdentityKey");
contentValues.put(Im.DATA, Base64.encodeBytes(identityKey.serialize()));
context.getContentResolver().insert(Data.CONTENT_URI, contentValues);
}
}
@Override
public IdentityKey importIdentityKey(Context context, Uri uri) {
long contactId = getContactIdFromLookupUri(context, uri);
String selection = Im.CONTACT_ID + " = ? AND " + Im.PROTOCOL + " = ? AND " + Im.CUSTOM_PROTOCOL + " = ?";
String[] selectionArgs = new String[] {contactId+"", Im.PROTOCOL_CUSTOM+"", "TextSecure-IdentityKey"};
Cursor cursor = context.getContentResolver().query(Data.CONTENT_URI, null, selection, selectionArgs, null);
try {
if (cursor != null && cursor.moveToFirst()) {
String data = cursor.getString(cursor.getColumnIndexOrThrow(Im.DATA));
if (data != null)
return new IdentityKey(Base64.decode(data), 0);
}
} catch (InvalidKeyException e) {
Log.w("ContactAccessorNewApi", e);
return null;
} catch (IOException e) {
Log.w("ContactAccessorNewApi", e);
return null;
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
@Override
public String getNameFromContact(Context context, Uri uri) {
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(uri, new String[] {Contacts.DISPLAY_NAME}, null, null, null);
if (cursor != null && cursor.moveToFirst())
return cursor.getString(0);
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
private String getMobileNumberForId(Context context, long id) {
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(Phone.CONTENT_URI, null, Phone.CONTACT_ID + " = ? AND " + Phone.TYPE + " = ?",
new String[] {id+"", Phone.TYPE_MOBILE+""}, null);
if (cursor != null && cursor.moveToFirst())
return cursor.getString(cursor.getColumnIndexOrThrow(Phone.NUMBER));
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
@Override
public NameAndNumber getNameAndNumberFromContact(Context context, Uri uri) {
Log.w("ContactAccessorNewApi", "Get name and number from: " + uri.toString());
Cursor cursor = null;
try {
NameAndNumber results = new NameAndNumber();
cursor = context.getContentResolver().query(uri, new String[] {Contacts._ID, Contacts.DISPLAY_NAME}, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
results.name = cursor.getString(1);
results.number = getMobileNumberForId(context, cursor.getLong(0));
return results;
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
@Override
public CursorLoader getCursorLoaderForContactsWithNumbers(Context context) {
Uri uri = ContactsContract.Contacts.CONTENT_URI;
String selection = ContactsContract.Contacts.HAS_PHONE_NUMBER + " = 1";
return new CursorLoader(context, uri, null, selection, null,
ContactsContract.Contacts.DISPLAY_NAME + " ASC");
}
@Override
public Cursor getCursorForContactsWithNumbers(Context context) {
Uri uri = ContactsContract.Contacts.CONTENT_URI;
String selection = ContactsContract.Contacts.HAS_PHONE_NUMBER + " = 1";
return context.getContentResolver().query(uri, null, selection, null,
ContactsContract.Contacts.DISPLAY_NAME + " ASC");
}
private ContactData getContactData(Context context, String displayName, long id) {
ContactData contactData = new ContactData();
contactData.id = id;
contactData.name = displayName;
contactData.numbers = new LinkedList<NumberData>();
Cursor numberCursor = null;
try {
numberCursor = context.getContentResolver().query(Phone.CONTENT_URI, null, Phone.CONTACT_ID + " = ?",
new String[] {contactData.id + ""}, null);
while (numberCursor != null && numberCursor.moveToNext())
contactData.numbers.add(new NumberData(Phone.getTypeLabel(context.getResources(),
numberCursor.getInt(numberCursor.getColumnIndexOrThrow(Phone.TYPE)),
numberCursor.getString(numberCursor.getColumnIndexOrThrow(Phone.LABEL))).toString(),
numberCursor.getString(numberCursor.getColumnIndexOrThrow(Phone.NUMBER))));
} finally {
if (numberCursor != null)
numberCursor.close();
}
return contactData;
}
@Override
public ContactData getContactData(Context context, Cursor cursor) {
return getContactData(context,
cursor.getString(cursor.getColumnIndexOrThrow(Contacts.DISPLAY_NAME)),
cursor.getLong(cursor.getColumnIndexOrThrow(Contacts._ID)));
}
@Override
public Cursor getCursorForContactGroups(Context context) {
return context.getContentResolver().query(ContactsContract.Groups.CONTENT_URI, null, null, null, ContactsContract.Groups.TITLE + " ASC");
}
@Override
public CursorLoader getCursorLoaderForContactGroups(Context context) {
return new CursorLoader(context, ContactsContract.Groups.CONTENT_URI,
null, null, null, ContactsContract.Groups.TITLE + " ASC");
}
@Override
public List<ContactData> getGroupMembership(Context context, long groupId) {
LinkedList<ContactData> contacts = new LinkedList<ContactData>();
Cursor groupMembership = null;
try {
String selection = ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID + " = ? AND " +
ContactsContract.CommonDataKinds.GroupMembership.MIMETYPE + " = ?";
String[] args = new String[] {groupId+"",
ContactsContract.CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE};
groupMembership = context.getContentResolver().query(Data.CONTENT_URI, null, selection, args, null);
while (groupMembership != null && groupMembership.moveToNext()) {
String displayName = groupMembership.getString(groupMembership.getColumnIndexOrThrow(Data.DISPLAY_NAME));
long contactId = groupMembership.getLong(groupMembership.getColumnIndexOrThrow(Data.CONTACT_ID));
contacts.add(getContactData(context, displayName, contactId));
}
} finally {
if (groupMembership != null)
groupMembership.close();
}
return contacts;
}
@Override
public GroupData getGroupData(Context context, Cursor cursor) {
GroupData groupData = new GroupData();
groupData.id = cursor.getLong(cursor.getColumnIndexOrThrow(ContactsContract.Groups._ID));
groupData.name = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Groups.TITLE));
return groupData;
}
@Override
public String getNameForNumber(Context context, String number) {
Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
try {
if (cursor != null && cursor.moveToFirst())
return cursor.getString(cursor.getColumnIndexOrThrow(PhoneLookup.DISPLAY_NAME));
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
@Override
public Uri getContactsUri() {
return ContactsContract.Contacts.CONTENT_URI;
}
}

View File

@@ -0,0 +1,28 @@
package org.thoughtcrime.securesms.contacts;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import java.util.List;
public abstract class ContactIdentityManager {
public static ContactIdentityManager getInstance(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
return new ContactIdentityManagerICS(context);
else
return new ContactIdentityManagerGingerbread(context);
}
protected final Context context;
public ContactIdentityManager(Context context) {
this.context = context.getApplicationContext();
}
public abstract Uri getSelfIdentityUri();
public abstract boolean isSelfIdentityAutoDetected();
public abstract List<Long> getSelfIdentityRawContactIds();
}

View File

@@ -0,0 +1,137 @@
package org.thoughtcrime.securesms.contacts;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.preference.PreferenceManager;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.PhoneLookup;
import android.provider.ContactsContract.RawContacts;
import android.telephony.TelephonyManager;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import java.util.ArrayList;
import java.util.List;
class ContactIdentityManagerGingerbread extends ContactIdentityManager {
public ContactIdentityManagerGingerbread(Context context) {
super(context);
}
@Override
public Uri getSelfIdentityUri() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
String contactUriString = preferences.getString(ApplicationPreferencesActivity.IDENTITY_PREF, null);
if (hasLocalNumber()) {
return getContactUriForNumber(getLocalNumber());
} else if (contactUriString != null) {
return Uri.parse(contactUriString);
}
return null;
}
@Override
public boolean isSelfIdentityAutoDetected() {
return hasLocalNumber() && getContactUriForNumber(getLocalNumber()) != null;
}
@Override
public List<Long> getSelfIdentityRawContactIds() {
long selfIdentityContactId = getSelfIdentityContactId();
if (selfIdentityContactId == -1)
return null;
Cursor cursor = null;
ArrayList<Long> rawContactIds = new ArrayList<Long>();
try {
cursor = context.getContentResolver().query(RawContacts.CONTENT_URI,
new String[] {RawContacts._ID},
RawContacts.CONTACT_ID + " = ?",
new String[] {selfIdentityContactId+""},
null);
if (cursor == null || cursor.getCount() == 0)
return null;
while (cursor.moveToNext()) {
rawContactIds.add(Long.valueOf(cursor.getLong(0)));
}
return rawContactIds;
} finally {
if (cursor != null)
cursor.close();
}
}
private Uri getContactUriForNumber(String number) {
String[] PROJECTION = new String[] {
PhoneLookup.DISPLAY_NAME,
PhoneLookup.LOOKUP_KEY,
PhoneLookup._ID,
};
Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(uri, PROJECTION, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
return Contacts.getLookupUri(cursor.getLong(2), cursor.getString(1));
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
private long getSelfIdentityContactId() {
Uri contactUri = getSelfIdentityUri();
if (contactUri == null)
return -1;
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(contactUri,
new String[] {ContactsContract.Contacts._ID},
null, null, null);
if (cursor != null && cursor.moveToFirst()) {
return cursor.getLong(0);
} else {
return -1;
}
} finally {
if (cursor != null)
cursor.close();
}
}
private String getLocalNumber() {
return ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE))
.getLine1Number();
}
private boolean hasLocalNumber() {
String number = getLocalNumber();
return (number != null) && (number.trim().length() > 0);
}
}

View File

@@ -0,0 +1,79 @@
package org.thoughtcrime.securesms.contacts;
import android.annotation.SuppressLint;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.PhoneLookup;
import java.util.LinkedList;
import java.util.List;
class ContactIdentityManagerICS extends ContactIdentityManager {
public ContactIdentityManagerICS(Context context) {
super(context);
}
@SuppressLint("NewApi")
@Override
public Uri getSelfIdentityUri() {
String[] PROJECTION = new String[] {
PhoneLookup.DISPLAY_NAME,
PhoneLookup.LOOKUP_KEY,
PhoneLookup._ID,
};
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(ContactsContract.Profile.CONTENT_URI,
PROJECTION, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
return Contacts.getLookupUri(cursor.getLong(2), cursor.getString(1));
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
@Override
public boolean isSelfIdentityAutoDetected() {
return true;
}
@SuppressLint("NewApi")
@Override
public List<Long> getSelfIdentityRawContactIds() {
List<Long> results = new LinkedList<Long>();
String[] PROJECTION = new String[] {
ContactsContract.Profile._ID
};
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI,
PROJECTION, null, null, null);
if (cursor == null || cursor.getCount() == 0)
return null;
while (cursor.moveToNext()) {
results.add(cursor.getLong(0));
}
return results;
} finally {
if (cursor != null)
cursor.close();
}
}
}

View File

@@ -58,7 +58,7 @@ public class KeyExchangeInitiator {
LinkedList<Recipient> list = new LinkedList<Recipient>();
list.add(recipient);
MessageSender.send(context, masterSecret, new Recipients(list), -1, message.serialize(), false);
MessageSender.send(context, masterSecret, new Recipients(list), -1, message.serialize(), true);
}
private static boolean hasInitiatedSession(Context context, MasterSecret masterSecret, Recipient recipient) {

View File

@@ -1,6 +1,6 @@
/**
/**
* 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
@@ -10,14 +10,15 @@
* 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.crypto;
import java.util.LinkedList;
import java.util.List;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import org.bouncycastle.util.Arrays;
import org.thoughtcrime.securesms.database.LocalKeyRecord;
@@ -30,18 +31,17 @@ import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.util.Conversions;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import java.util.LinkedList;
import java.util.List;
/**
* This class processes key exchange interactions.
*
*
* @author Moxie Marlinspike
*/
public class KeyExchangeProcessor {
public static final String SECURITY_UPDATE_EVENT = "org.thoughtcrime.securesms.KEY_EXCHANGE_UPDATE";
private Context context;
@@ -55,7 +55,7 @@ public class KeyExchangeProcessor {
this.context = context;
this.recipient = recipient;
this.masterSecret = masterSecret;
this.remoteKeyRecord = new RemoteKeyRecord(context, recipient);
this.localKeyRecord = new LocalKeyRecord(context, masterSecret, recipient);
this.sessionRecord = new SessionRecord(context, masterSecret, recipient);
@@ -64,19 +64,19 @@ public class KeyExchangeProcessor {
public boolean hasCompletedSession() {
return sessionRecord.getLocalFingerprint() != null;
}
public boolean hasSameSessionIdentity(KeyExchangeMessage message) {
return
return
(this.sessionRecord.getIdentityKey() != null) &&
(message.getIdentityKey() != null) &&
(this.sessionRecord.getIdentityKey().equals(message.getIdentityKey()) &&
!isRemoteKeyExchangeForExistingSession(message));
}
public boolean hasInitiatedSession() {
return localKeyRecord.getCurrentKeyPair() != null;
}
private boolean needsResponseFromUs() {
return !hasInitiatedSession() || remoteKeyRecord.getCurrentRemoteKey() != null;
}
@@ -84,56 +84,56 @@ public class KeyExchangeProcessor {
public boolean isRemoteKeyExchangeForExistingSession(KeyExchangeMessage message) {
return Arrays.areEqual(message.getPublicKey().getFingerprintBytes(), sessionRecord.getRemoteFingerprint());
}
public boolean isLocalKeyExchangeForExistingSession(KeyExchangeMessage message) {
return Arrays.areEqual(message.getPublicKey().getFingerprintBytes(), sessionRecord.getLocalFingerprint());
}
public boolean isStale(KeyExchangeMessage message) {
int responseKeyId = Conversions.highBitsToMedium(message.getPublicKey().getId());
Log.w("KeyExchangeProcessor", "Key Exchange High ID Bits: " + responseKeyId);
return responseKeyId != 0 &&
(localKeyRecord.getCurrentKeyPair() != null && localKeyRecord.getCurrentKeyPair().getId() != responseKeyId);
}
public boolean processKeyExchangeMessage(KeyExchangeMessage message, long threadId) {
int initiateKeyId = Conversions.lowBitsToMedium(message.getPublicKey().getId());
int initiateKeyId = Conversions.lowBitsToMedium(message.getPublicKey().getId());
message.getPublicKey().setId(initiateKeyId);
if (needsResponseFromUs()) {
List<Recipient> recipients = new LinkedList<Recipient>();
recipients.add(recipient);
localKeyRecord = KeyUtil.initializeRecordFor(recipient, context, masterSecret);
KeyExchangeMessage ourMessage = new KeyExchangeMessage(context, masterSecret, Math.min(Message.SUPPORTED_VERSION, message.getMaxVersion()), localKeyRecord, initiateKeyId);
Log.w("KeyExchangeProcessor", "Responding with key exchange message fingerprint: " + ourMessage.getPublicKey().getFingerprint());
Log.w("KeyExchangeProcessor", "Which has a local key record fingerprint: " + localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprint());
MessageSender.send(context, masterSecret, new Recipients(recipients), threadId, ourMessage.serialize(), false);
MessageSender.send(context, masterSecret, new Recipients(recipients), threadId, ourMessage.serialize(), true);
}
remoteKeyRecord.setCurrentRemoteKey(message.getPublicKey());
remoteKeyRecord.setLastRemoteKey(message.getPublicKey());
remoteKeyRecord.save();
sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(),
remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes());
sessionRecord.setIdentityKey(message.getIdentityKey());
sessionRecord.setSessionVersion(Math.min(Message.SUPPORTED_VERSION, message.getMaxVersion()));
Log.w("KeyExchangeUtil", "Setting session version: " + Math.min(Message.SUPPORTED_VERSION, message.getMaxVersion()));
sessionRecord.save();
sessionRecord.save();
DecryptingQueue.scheduleRogueMessages(context, masterSecret, recipient);
Intent intent = new Intent(SECURITY_UPDATE_EVENT);
intent.putExtra("thread_id", threadId);
intent.setPackage(context.getPackageName());
context.sendBroadcast(intent, KeyCachingService.KEY_PERMISSION);
return true;
}
}

View File

@@ -16,11 +16,6 @@
*/
package org.thoughtcrime.securesms.crypto;
import android.content.Context;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.MessageRecord;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.protocol.Prefix;
import org.thoughtcrime.securesms.util.InvalidMessageException;
@@ -38,30 +33,6 @@ public class MessageDisplayHelper {
}
};
private static boolean isUnreadableAsymmetricMessage(long type) {
return type == SmsDatabase.Types.FAILED_DECRYPT_TYPE;
}
private static boolean isInProcessAsymmetricMessage(String body, long type) {
return type == SmsDatabase.Types.DECRYPT_IN_PROGRESS_TYPE || (type == 0 && body.startsWith(Prefix.ASYMMETRIC_ENCRYPT)) || (type == 0 && body.startsWith(Prefix.ASYMMETRIC_LOCAL_ENCRYPT));
}
private static boolean isRogueAsymmetricMessage(long type) {
return type == SmsDatabase.Types.NO_SESSION_TYPE;
}
private static boolean isKeyExchange(String body) {
return body.startsWith(Prefix.KEY_EXCHANGE);
}
private static boolean isProcessedKeyExchange(String body) {
return body.startsWith(Prefix.PROCESSED_KEY_EXCHANGE);
}
private static boolean isStaleKeyExchange(String body) {
return body.startsWith(Prefix.STALE_KEY_EXCHANGE);
}
private static String checkCacheForBody(String body) {
if (decryptedBodyCache.containsKey(body)) {
String decryptedBody = decryptedBodyCache.get(body).get();
@@ -76,50 +47,19 @@ public class MessageDisplayHelper {
return null;
}
public static void setDecryptedMessageBody(Context context, String body,
MessageRecord message, MasterCipher bodyCipher)
{
try {
if (body.startsWith(Prefix.SYMMETRIC_ENCRYPT)) {
String cacheResult = checkCacheForBody(body);
if (cacheResult != null) {
body = cacheResult;
} else {
String decryptedBody = bodyCipher.decryptBody(body.substring(Prefix.SYMMETRIC_ENCRYPT.length()));
decryptedBodyCache.put(body, new SoftReference<String>(decryptedBody));
body = decryptedBody;
}
}
public static String getDecryptedMessageBody(MasterCipher bodyCipher, String body) throws InvalidMessageException {
if (body.startsWith(Prefix.SYMMETRIC_ENCRYPT)) {
String cacheResult = checkCacheForBody(body);
if (isUnreadableAsymmetricMessage(message.getType())) {
message.setBody(context.getString(R.string.MessageDisplayHelper_bad_encrypted_message));
message.setEmphasis(true);
} else if (isInProcessAsymmetricMessage(body, message.getType())) {
message.setBody(context.getString(R.string.MessageDisplayHelper_decrypting_please_wait));
message.setEmphasis(true);
} else if (isRogueAsymmetricMessage(message.getType())) {
message.setBody(context.getString(R.string.MessageDisplayHelper_message_encrypted_for_non_existing_session));
message.setEmphasis(true);
} else if (isKeyExchange(body)) {
message.setKeyExchange(true);
message.setEmphasis(true);
message.setBody(body);
} else if (isProcessedKeyExchange(body)) {
message.setProcessedKeyExchange(true);
message.setEmphasis(true);
message.setBody(body);
} else if (isStaleKeyExchange(body)) {
message.setStaleKeyExchange(true);
message.setEmphasis(true);
message.setBody(body);
} else {
message.setBody(body);
message.setEmphasis(false);
}
} catch (InvalidMessageException ime) {
message.setBody(context.getString(R.string.MessageDisplayHelper_decryption_error_local_message_corrupted_mac_doesn_t_match_potential_tampering_question));
message.setEmphasis(true);
if (cacheResult != null)
return cacheResult;
String decryptedBody = bodyCipher.decryptBody(body.substring(Prefix.SYMMETRIC_ENCRYPT.length()));
decryptedBodyCache.put(body, new SoftReference<String>(decryptedBody));
return decryptedBody;
}
}
return body;
}
}

View File

@@ -86,7 +86,7 @@ public class CanonicalAddressDatabase {
}
} finally {
if (cursor != null)
cursor.close();
cursor.close();
}
}
@@ -154,12 +154,12 @@ public class CanonicalAddressDatabase {
}
@Override
public void onCreate(SQLiteDatabase db) {
public void onCreate(SQLiteDatabase db) {
db.execSQL(DATABASE_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}

View File

@@ -1,6 +1,6 @@
/**
/**
* 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
@@ -10,40 +10,41 @@
* 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.database;
import java.io.UnsupportedEncodingException;
import android.content.ContentValues;
import android.util.Log;
import ws.com.google.android.mms.pdu.CharacterSets;
import ws.com.google.android.mms.pdu.EncodedStringValue;
import android.content.ContentValues;
import android.util.Log;
import java.io.UnsupportedEncodingException;
public class ContentValuesBuilder {
private final ContentValues contentValues;
public ContentValuesBuilder(ContentValues contentValues) {
this.contentValues = contentValues;
}
public void add(String key, String charsetKey, EncodedStringValue value) {
if (value != null) {
contentValues.put(key, toIsoString(value.getTextString()));
contentValues.put(charsetKey, value.getCharacterSet());
}
}
public void add(String contentKey, byte[] value) {
if (value != null) {
contentValues.put(contentKey, toIsoString(value));
}
}
public void add(String contentKey, int b) {
if (b != 0)
contentValues.put(contentKey, b);
@@ -53,11 +54,11 @@ public class ContentValuesBuilder {
if (value != -1L)
contentValues.put(contentKey, value);
}
public ContentValues getContentValues() {
return contentValues;
}
private String toIsoString(byte[] bytes) {
try {
return new String(bytes, CharacterSets.MIMENAME_ISO_8859_1);

View File

@@ -1,6 +1,6 @@
/**
/**
* 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
@@ -10,50 +10,50 @@
* 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.database;
import java.util.Set;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import java.util.Set;
public abstract class Database {
protected static final String ID_WHERE = "_id = ?";
private static final String CONVERSATION_URI = "content://textsecure/thread/";
private static final String CONVERSATION_LIST_URI = "content://textsecure/conversation-list";
protected final SQLiteOpenHelper databaseHelper;
protected final Context context;
public Database(Context context, SQLiteOpenHelper databaseHelper) {
this.context = context;
this.databaseHelper = databaseHelper;
}
protected void notifyConversationListeners(Set<Long> threadIds) {
for (long threadId : threadIds)
notifyConversationListeners(threadId);
}
protected void notifyConversationListeners(long threadId) {
context.getContentResolver().notifyChange(Uri.parse(CONVERSATION_URI + threadId), null);
}
protected void notifyConversationListListeners() {
context.getContentResolver().notifyChange(Uri.parse(CONVERSATION_LIST_URI), null);
}
protected void setNotifyConverationListeners(Cursor cursor, long threadId) {
cursor.setNotificationUri(context.getContentResolver(), Uri.parse(CONVERSATION_URI + threadId));
}
protected void setNotifyConverationListListeners(Cursor cursor) {
cursor.setNotificationUri(context.getContentResolver(), Uri.parse(CONVERSATION_LIST_URI));
}

View File

@@ -1,6 +1,6 @@
/**
/**
* 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
@@ -10,30 +10,32 @@
* 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.database;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
import org.thoughtcrime.securesms.crypto.MasterSecret;
public class DatabaseFactory {
private static final int INTRODUCED_IDENTITIES_VERSION = 2;
private static final int DATABASE_VERSION = 2;
private static final int INTRODUCED_IDENTITIES_VERSION = 2;
private static final int INTRODUCED_INDEXES_VERSION = 3;
private static final int DATABASE_VERSION = 3;
private static final String DATABASE_NAME = "messages.db";
private static final Object lock = new Object();
private static DatabaseFactory instance;
private static EncryptingMmsDatabase encryptingMmsInstance;
private static EncryptingPartDatabase encryptingPartInstance;
private final DatabaseHelper databaseHelper;
private final SmsDatabase sms;
@@ -45,70 +47,70 @@ public class DatabaseFactory {
private final MmsAddressDatabase mmsAddress;
private final MmsSmsDatabase mmsSmsDatabase;
private final IdentityDatabase identityDatabase;
public static DatabaseFactory getInstance(Context context) {
synchronized (lock) {
if (instance == null)
instance = new DatabaseFactory(context);
return instance;
}
}
public static MmsSmsDatabase getMmsSmsDatabase(Context context) {
return getInstance(context).mmsSmsDatabase;
}
public static ThreadDatabase getThreadDatabase(Context context) {
return getInstance(context).thread;
}
public static SmsDatabase getSmsDatabase(Context context) {
return getInstance(context).sms;
}
public static MmsDatabase getMmsDatabase(Context context) {
return getInstance(context).mms;
}
public static CanonicalAddressDatabase getAddressDatabase(Context context) {
return getInstance(context).address;
}
public static EncryptingSmsDatabase getEncryptingSmsDatabase(Context context) {
return getInstance(context).encryptingSms;
}
public static EncryptingMmsDatabase getEncryptingMmsDatabase(Context context, MasterSecret masterSecret) {
synchronized (lock) {
if (encryptingMmsInstance == null) {
DatabaseFactory factory = getInstance(context);
encryptingMmsInstance = new EncryptingMmsDatabase(context, factory.databaseHelper, masterSecret);
}
return encryptingMmsInstance;
}
}
public static PartDatabase getPartDatabase(Context context) {
return getInstance(context).part;
}
public static EncryptingPartDatabase getEncryptingPartDatabase(Context context, MasterSecret masterSecret) {
synchronized (lock) {
if (encryptingPartInstance == null) {
DatabaseFactory factory = getInstance(context);
encryptingPartInstance = new EncryptingPartDatabase(context, factory.databaseHelper, masterSecret);
encryptingPartInstance = new EncryptingPartDatabase(context, factory.databaseHelper, masterSecret);
}
return encryptingPartInstance;
}
}
public static MmsAddressDatabase getMmsAddressDatabase(Context context) {
return getInstance(context).mmsAddress;
}
public static IdentityDatabase getIdentityDatabase(Context context) {
return getInstance(context).identityDatabase;
}
@@ -125,13 +127,13 @@ public class DatabaseFactory {
this.mmsSmsDatabase = new MmsSmsDatabase(context, databaseHelper);
this.identityDatabase = new IdentityDatabase(context, databaseHelper);
}
public void close() {
databaseHelper.close();
address.close();
instance = null;
}
private static class DatabaseHelper extends SQLiteOpenHelper {
public DatabaseHelper(Context context, String name, CursorFactory factory, int version) {
@@ -139,21 +141,42 @@ public class DatabaseFactory {
}
@Override
public void onCreate(SQLiteDatabase db) {
public void onCreate(SQLiteDatabase db) {
db.execSQL(SmsDatabase.CREATE_TABLE);
db.execSQL(MmsDatabase.CREATE_TABLE);
db.execSQL(PartDatabase.CREATE_TABLE);
db.execSQL(ThreadDatabase.CREATE_TABLE);
db.execSQL(MmsAddressDatabase.CREATE_TABLE);
db.execSQL(IdentityDatabase.CREATE_TABLE);
// db.execSQL(CanonicalAddress.CREATE_TABLE);
executeStatements(db, SmsDatabase.CREATE_INDEXS);
executeStatements(db, MmsDatabase.CREATE_INDEXS);
executeStatements(db, PartDatabase.CREATE_INDEXS);
executeStatements(db, ThreadDatabase.CREATE_INDEXS);
executeStatements(db, MmsAddressDatabase.CREATE_INDEXS);
// db.execSQL(CanonicalAddress.CREATE_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion < INTRODUCED_IDENTITIES_VERSION)
db.execSQL(IdentityDatabase.CREATE_TABLE);
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion < INTRODUCED_IDENTITIES_VERSION) {
db.execSQL(IdentityDatabase.CREATE_TABLE);
}
if (oldVersion < INTRODUCED_INDEXES_VERSION) {
executeStatements(db, SmsDatabase.CREATE_INDEXS);
executeStatements(db, MmsDatabase.CREATE_INDEXS);
executeStatements(db, PartDatabase.CREATE_INDEXS);
executeStatements(db, ThreadDatabase.CREATE_INDEXS);
executeStatements(db, MmsAddressDatabase.CREATE_INDEXS);
}
}
private void executeStatements(SQLiteDatabase db, String[] statements) {
for (String statement : statements)
db.execSQL(statement);
}
}
}

View File

@@ -1,6 +1,6 @@
/**
/**
* 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
@@ -10,28 +10,28 @@
* 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.database;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import android.content.Context;
import android.database.sqlite.SQLiteOpenHelper;
import org.thoughtcrime.securesms.crypto.MasterSecret;
public class EncryptingMmsDatabase extends MmsDatabase {
private final MasterSecret masterSecret;
public EncryptingMmsDatabase(Context context, SQLiteOpenHelper databaseHelper, MasterSecret masterSecret) {
super(context, databaseHelper);
this.masterSecret = masterSecret;
}
@Override
protected PartDatabase getPartDatabase() {
protected PartDatabase getPartDatabase() {
return DatabaseFactory.getEncryptingPartDatabase(context, masterSecret);
}

View File

@@ -1,6 +1,6 @@
/**
/**
* 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
@@ -10,30 +10,31 @@
* 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.database;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import android.content.Context;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream;
import org.thoughtcrime.securesms.crypto.EncryptingPartOutputStream;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import ws.com.google.android.mms.pdu.PduPart;
import android.content.Context;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
public class EncryptingPartDatabase extends PartDatabase {
private final MasterSecret masterSecret;
public EncryptingPartDatabase(Context context, SQLiteOpenHelper databaseHelper, MasterSecret masterSecret) {
super(context, databaseHelper);
this.masterSecret = masterSecret;
@@ -44,14 +45,14 @@ public class EncryptingPartDatabase extends PartDatabase {
Log.w("EncryptingPartDatabase", "Getting part at: " + path.getAbsolutePath());
if (!part.getEncrypted())
return super.getPartInputStream(path, part);
return new DecryptingPartInputStream(path, masterSecret);
}
@Override
protected FileOutputStream getPartOutputStream(File path, PduPart part) throws FileNotFoundException {
Log.w("EncryptingPartDatabase", "Writing part to: " + path.getAbsolutePath());
part.setEncrypted(true);
return new EncryptingPartOutputStream(path, masterSecret);
}
}
}

View File

@@ -1,6 +1,6 @@
/**
/**
* 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
@@ -10,66 +10,66 @@
* 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.database;
import android.content.Context;
import android.database.sqlite.SQLiteOpenHelper;
import android.telephony.SmsMessage;
import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher;
import org.thoughtcrime.securesms.crypto.AsymmetricMasterSecret;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.protocol.Prefix;
import android.content.Context;
import android.database.sqlite.SQLiteOpenHelper;
import android.telephony.SmsMessage;
public class EncryptingSmsDatabase extends SmsDatabase {
public EncryptingSmsDatabase(Context context, SQLiteOpenHelper databaseHelper) {
super(context, databaseHelper);
}
}
private String getAsymmetricEncryptedBody(AsymmetricMasterSecret masterSecret, String body) {
AsymmetricMasterCipher bodyCipher = new AsymmetricMasterCipher(masterSecret);
return Prefix.ASYMMETRIC_LOCAL_ENCRYPT + bodyCipher.encryptBody(body);
}
private String getEncryptedBody(MasterSecret masterSecret, String body) {
MasterCipher bodyCipher = new MasterCipher(masterSecret);
return Prefix.SYMMETRIC_ENCRYPT + bodyCipher.encryptBody(body);
return Prefix.SYMMETRIC_ENCRYPT + bodyCipher.encryptBody(body);
}
private long insertMessageSent(MasterSecret masterSecret, String address, long threadId, String body, long date, int type) {
String encryptedBody = getEncryptedBody(masterSecret, body);
return insertMessageSent(address, threadId, encryptedBody, date, type);
return insertMessageSent(address, threadId, encryptedBody, date, type);
}
public void updateSecureMessageBody(MasterSecret masterSecret, long messageId, String body) {
String encryptedBody = getEncryptedBody(masterSecret, body);
updateMessageBodyAndType(messageId, encryptedBody, Types.SECURE_RECEIVED_TYPE);
}
public void updateMessageBody(MasterSecret masterSecret, long messageId, String body) {
String encryptedBody = getEncryptedBody(masterSecret, body);
updateMessageBodyAndType(messageId, encryptedBody, Types.INBOX_TYPE);
}
public long insertMessageSent(MasterSecret masterSecret, String address, long threadId, String body, long date) {
return insertMessageSent(masterSecret, address, threadId, body, date, Types.ENCRYPTED_OUTBOX_TYPE);
}
public long insertSecureMessageSent(MasterSecret masterSecret, String address, long threadId, String body, long date) {
return insertMessageSent(masterSecret, address, threadId, body, date, Types.ENCRYPTING_TYPE);
}
public long insertMessageReceived(MasterSecret masterSecret, SmsMessage message, String body) {
String encryptedBody = getEncryptedBody(masterSecret, body);
return insertMessageReceived(message, encryptedBody);
}
public long insertMessageReceived(AsymmetricMasterSecret masterSecret, SmsMessage message, String body) {
String encryptedBody = getAsymmetricEncryptedBody(masterSecret, body);
return insertSecureMessageReceived(message, encryptedBody);

View File

@@ -1,181 +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.database;
import org.thoughtcrime.securesms.ConversationItem;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
public class MessageRecord {
private long id;
private long threadId;
private Recipient messageRecipient;
private Recipients recipients;
private String body;
private long date;
private long count;
private boolean read;
private long type;
private boolean emphasis;
private boolean keyExchange;
private boolean processedKeyExchange;
private boolean staleKeyExchange;
public MessageRecord(MessageRecord copy) {
this.id = copy.id;
this.threadId = copy.threadId;
this.messageRecipient = copy.messageRecipient;
this.recipients = copy.recipients;
this.body = copy.body;
this.date = copy.date;
this.count = copy.count;
this.read = copy.read;
this.type = copy.type;
this.emphasis = copy.emphasis;
this.keyExchange = copy.keyExchange;
this.processedKeyExchange = copy.processedKeyExchange;
}
public MessageRecord(long id, Recipients recipients, long date, long type, long threadId) {
this.id = id;
this.date = date;
this.type = type;
this.recipients = recipients;
this.threadId = threadId;
}
public MessageRecord(long id, Recipients recipients, long date, long count, boolean read, long threadId) {
this.id = id;
this.threadId = threadId;
this.recipients = recipients;
this.date = date;
this.count = count;
this.read = read;
}
public void setOnConversationItem(ConversationItem item) {
item.setMessageRecord(this);
}
public boolean isMms() {
return false;
}
public long getType() {
return type;
}
public void setMessageRecipient(Recipient recipient) {
this.messageRecipient = recipient;
}
public Recipient getMessageRecipient() {
return this.messageRecipient;
}
public void setEmphasis(boolean emphasis) {
this.emphasis = emphasis;
}
public boolean getEmphasis() {
return this.emphasis;
}
public void setId(long id) {
this.id = id;
}
public void setBody(String body) {
this.body = body;
}
public long getThreadId() {
return threadId;
}
public long getId() {
return id;
}
public Recipients getRecipients() {
return recipients;
}
public String getBody() {
return body;
}
public long getDate() {
return date;
}
public long getCount() {
return count;
}
public boolean getRead() {
return read;
}
public boolean isStaleKeyExchange() {
return this.staleKeyExchange;
}
public void setStaleKeyExchange(boolean staleKeyExchange) {
this.staleKeyExchange = staleKeyExchange;
}
public boolean isProcessedKeyExchange() {
return processedKeyExchange;
}
public void setProcessedKeyExchange(boolean processedKeyExchange) {
this.processedKeyExchange = processedKeyExchange;
}
public boolean isKeyExchange() {
return keyExchange || processedKeyExchange || staleKeyExchange;
}
public void setKeyExchange(boolean keyExchange) {
this.keyExchange = keyExchange;
}
public boolean isFailedDecryptType() {
return type == SmsDatabase.Types.FAILED_DECRYPT_TYPE;
}
public boolean isFailed() {
return SmsDatabase.Types.isFailedMessageType(type);
}
public boolean isOutgoing() {
return SmsDatabase.Types.isOutgoingMessageType(type);
}
public boolean isPending() {
return SmsDatabase.Types.isPendingMessageType(type);
}
public boolean isSecure() {
return SmsDatabase.Types.isSecureType(type);
}
}

View File

@@ -1,6 +1,6 @@
/**
/**
* 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
@@ -10,17 +10,12 @@
* 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.database;
import java.io.UnsupportedEncodingException;
import ws.com.google.android.mms.pdu.CharacterSets;
import ws.com.google.android.mms.pdu.EncodedStringValue;
import ws.com.google.android.mms.pdu.PduHeaders;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@@ -28,19 +23,29 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import ws.com.google.android.mms.pdu.CharacterSets;
import ws.com.google.android.mms.pdu.EncodedStringValue;
import ws.com.google.android.mms.pdu.PduHeaders;
import java.io.UnsupportedEncodingException;
public class MmsAddressDatabase extends Database {
private static final String TABLE_NAME = "mms_addresses";
private static final String TABLE_NAME = "mms_addresses";
private static final String ID = "_id";
private static final String MMS_ID = "mms_id";
private static final String TYPE = "type";
private static final String ADDRESS = "address";
private static final String ADDRESS = "address";
private static final String ADDRESS_CHARSET = "address_charset";
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
MMS_ID + " INTEGER, " + TYPE + " INTEGER, " + ADDRESS + " TEXT, " +
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
MMS_ID + " INTEGER, " + TYPE + " INTEGER, " + ADDRESS + " TEXT, " +
ADDRESS_CHARSET + " INTEGER);";
public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS mms_addresses_mms_id_index ON " + TABLE_NAME + " (" + MMS_ID + ");",
};
public MmsAddressDatabase(Context context, SQLiteOpenHelper databaseHelper) {
super(context, databaseHelper);
}
@@ -56,7 +61,7 @@ public class MmsAddressDatabase extends Database {
database.insert(TABLE_NAME, null, contentValues);
}
}
private void insertAddress(long messageId, int type, EncodedStringValue[] addresses) {
if (addresses != null) {
for (int i=0;i<addresses.length;i++) {
@@ -64,53 +69,53 @@ public class MmsAddressDatabase extends Database {
}
}
}
private void addAddress(Cursor cursor, PduHeaders headers) {
long type = cursor.getLong(cursor.getColumnIndexOrThrow(TYPE));
String address = cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS));
long charset = cursor.getLong(cursor.getColumnIndexOrThrow(ADDRESS_CHARSET));
EncodedStringValue encodedAddress = new EncodedStringValue((int)charset, getBytes(address));
if (type == PduHeaders.FROM)
headers.setEncodedStringValue(encodedAddress, PduHeaders.FROM);
else
headers.appendEncodedStringValue(encodedAddress, (int)type);
}
public void insertAddressesForId(long messageId, PduHeaders headers) {
insertAddress(messageId, PduHeaders.FROM, headers.getEncodedStringValue(PduHeaders.FROM));
insertAddress(messageId, PduHeaders.TO, headers.getEncodedStringValues(PduHeaders.TO));
insertAddress(messageId, PduHeaders.CC, headers.getEncodedStringValues(PduHeaders.CC));
insertAddress(messageId, PduHeaders.BCC, headers.getEncodedStringValues(PduHeaders.BCC));
}
public void getAddressesForId(long messageId, PduHeaders headers) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = null;
try {
cursor = database.query(TABLE_NAME, null, MMS_ID + " = ?", new String[] {messageId+""}, null, null, null);
while (cursor != null && cursor.moveToNext()) {
addAddress(cursor, headers);
}
} finally {
if (cursor != null)
cursor.close();
cursor.close();
}
}
public void deleteAddressesForId(long messageId) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
database.delete(TABLE_NAME, MMS_ID + " = ?", new String[] {messageId+""});
}
public void deleteAllAddresses() {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
database.delete(TABLE_NAME, null, null);
database.delete(TABLE_NAME, null, null);
}
private byte[] getBytes(String data) {
try {
return data.getBytes(CharacterSets.MIMENAME_ISO_8859_1);
@@ -120,7 +125,6 @@ public class MmsAddressDatabase extends Database {
}
}
private String toIsoString(byte[] bytes) {
try {
return new String(bytes, CharacterSets.MIMENAME_ISO_8859_1);
@@ -129,7 +133,4 @@ public class MmsAddressDatabase extends Database {
return "";
}
}
}

View File

@@ -49,7 +49,7 @@ public class MmsDatabase extends Database {
public static final String TABLE_NAME = "mms";
public static final String ID = "_id";
private static final String THREAD_ID = "thread_id";
private static final String DATE = "date";
public static final String DATE = "date";
public static final String MESSAGE_BOX = "msg_box";
private static final String READ = "read";
private static final String MESSAGE_ID = "m_id";
@@ -89,6 +89,13 @@ public class MmsDatabase extends Database {
CONTENT_CLASS + " INTEGER, " + RESPONSE_TEXT + " TEXT, " + DELIVERY_TIME + " INTEGER, " +
DELIVERY_REPORT + " INTEGER);";
public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS mms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");",
"CREATE INDEX IF NOT EXISTS mms_read_index ON " + TABLE_NAME + " (" + READ + ");",
"CREATE INDEX IF NOT EXISTS mms_read_and_thread_id_index ON " + TABLE_NAME + "(" + READ + "," + THREAD_ID + ");",
"CREATE INDEX IF NOT EXISTS mms_message_box_index ON " + TABLE_NAME + " (" + MESSAGE_BOX + ");"
};
public MmsDatabase(Context context, SQLiteOpenHelper databaseHelper) {
super(context, databaseHelper);
}
@@ -268,11 +275,11 @@ public class MmsDatabase extends Database {
while (cursor.moveToNext()) {
long messageId = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
long outboxType = cursor.getLong(cursor.getColumnIndexOrThrow(MESSAGE_BOX));
PduHeaders headers = getHeadersFromCursor(cursor);
addr.getAddressesForId(messageId, headers);
PduBody body = parts.getParts(messageId, true);
requests[i++] = new SendReq(headers, body, messageId, outboxType);
long outboxType = cursor.getLong(cursor.getColumnIndexOrThrow(MESSAGE_BOX));
PduHeaders headers = getHeadersFromCursor(cursor);
addr.getAddressesForId(messageId, headers);
PduBody body = parts.getParts(messageId, true);
requests[i++] = new SendReq(headers, body, messageId, outboxType);
}
return requests;
@@ -342,7 +349,7 @@ public class MmsDatabase extends Database {
ContentValues contentValues = getContentValuesFromHeader(headers);
if (!isSecure) contentValues.put(MESSAGE_BOX, Types.MESSAGE_BOX_OUTBOX);
else contentValues.put(MESSAGE_BOX, Types.MESSAGE_BOX_SECURE_OUTBOX);
else contentValues.put(MESSAGE_BOX, Types.MESSAGE_BOX_SECURE_OUTBOX);
contentValues.put(THREAD_ID, threadId);
contentValues.put(READ, 1);
@@ -412,7 +419,6 @@ public class MmsDatabase extends Database {
}
}
public void deleteAllThreads() {
DatabaseFactory.getPartDatabase(context).deleteAllParts();
DatabaseFactory.getMmsAddressDatabase(context).deleteAllAddresses();
@@ -421,9 +427,9 @@ public class MmsDatabase extends Database {
database.delete(TABLE_NAME, null, null);
}
public Cursor getCarrierMmsInformation() {
public Cursor getCarrierMmsInformation(String apn) {
Uri uri = Uri.withAppendedPath(Uri.parse("content://telephony/carriers"), "current");
String selection = "type = 'mms'";
String selection = (apn == null || apn.trim().length() == 0) ? null : String.format("apn = '%s'", apn.trim());
return context.getContentResolver().query(uri, null, selection, null, null);
}
@@ -448,7 +454,7 @@ public class MmsDatabase extends Database {
return headers;
} finally {
if (cursor != null)
cursor.close();
cursor.close();
}
}
@@ -535,9 +541,8 @@ public class MmsDatabase extends Database {
public static final int DOWNLOAD_INITIALIZED = 1;
public static final int DOWNLOAD_NO_CONNECTIVITY = 2;
public static final int DOWNLOAD_CONNECTING = 3;
public static final int DOWNLOAD_SOFT_FAILURE = 4;
public static final int DOWNLOAD_HARD_FAILURE = 5;
public static final int DOWNLOAD_SOFT_FAILURE = 4;
public static final int DOWNLOAD_HARD_FAILURE = 5;
public static boolean isSecureMmsBox(long mailbox) {
return mailbox == Types.MESSAGE_BOX_SECURE_OUTBOX || mailbox == Types.MESSAGE_BOX_SECURE_SENT || mailbox == Types.MESSAGE_BOX_SECURE_INBOX;
@@ -574,9 +579,5 @@ public class MmsDatabase extends Database {
public static boolean isHardError(int status) {
return status == DOWNLOAD_HARD_FAILURE;
}
}
}

View File

@@ -1,6 +1,6 @@
/**
/**
* 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
@@ -10,15 +10,12 @@
* 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.database;
import java.util.HashSet;
import java.util.Set;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
@@ -26,54 +23,109 @@ import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.util.Log;
import java.util.HashSet;
import java.util.Set;
public class MmsSmsDatabase extends Database {
public static final String TRANSPORT = "transport_type";
public static final String TRANSPORT = "transport_type";
public static final String GROUP_SIZE = "group_size";
public static final String SMS_GROUP_SENT_COUNT = "sms_group_sent_count";
public static final String SMS_GROUP_SEND_FAILED_COUNT = "sms_group_sent_failed_count";
public static final String MMS_GROUP_SENT_COUNT = "mms_group_sent_count";
public static final String MMS_GROUP_SEND_FAILED_COUNT = "mms_group_sent_failed_count";
public MmsSmsDatabase(Context context, SQLiteOpenHelper databaseHelper) {
super(context, databaseHelper);
}
public Cursor getConversation(long threadId) {
String[] projection = {"_id", "body", "type", "address", "subject", "normalized_date AS date", "m_type", "msg_box", "transport_type"};
String order = "normalized_date ASC";
String selection = "thread_id = " + threadId;
Cursor cursor = queryTables(projection, selection, order, null);
public Cursor getCollatedGroupConversation(long threadId) {
String smsCaseSecurity = "CASE " + SmsDatabase.TYPE + " " +
"WHEN " + SmsDatabase.Types.SENT_TYPE + " THEN 1 " +
"WHEN " + SmsDatabase.Types.SENT_PENDING + " THEN 1 " +
"WHEN " + SmsDatabase.Types.ENCRYPTED_OUTBOX_TYPE + " THEN 1 " +
"WHEN " + SmsDatabase.Types.FAILED_TYPE + " THEN 1 " +
"WHEN " + SmsDatabase.Types.ENCRYPTING_TYPE + " THEN 2 " +
"WHEN " + SmsDatabase.Types.SECURE_SENT_TYPE + " THEN 2 " +
"ELSE 0 END";
String mmsCaseSecurity = "CASE " + MmsDatabase.MESSAGE_BOX + " " +
"WHEN " + MmsDatabase.Types.MESSAGE_BOX_OUTBOX + " THEN 'insecure' " +
"WHEN " + MmsDatabase.Types.MESSAGE_BOX_SENT + " THEN 'insecure' " +
"WHEN " + MmsDatabase.Types.MESSAGE_BOX_SENT_FAILED + " THEN 'insecure' " +
"WHEN " + MmsDatabase.Types.MESSAGE_BOX_SECURE_OUTBOX + " THEN 'secure' " +
"WHEN " + MmsDatabase.Types.MESSAGE_BOX_SECURE_SENT + " THEN 'secure' " +
"ELSE 0 END";
String mmsGroupSentCount = "SUM(CASE " + MmsDatabase.MESSAGE_BOX + " " +
"WHEN " + MmsDatabase.Types.MESSAGE_BOX_SENT + " THEN 1 " +
"WHEN " + MmsDatabase.Types.MESSAGE_BOX_SECURE_SENT + " THEN 1 " +
"ELSE 0 END)";
String smsGroupSentCount = "SUM(CASE " + SmsDatabase.TYPE + " " +
"WHEN " + SmsDatabase.Types.SENT_TYPE + " THEN 1 " +
"WHEN " + SmsDatabase.Types.SECURE_SENT_TYPE + " THEN 1 " +
"ELSE 0 END)";
String mmsGroupSentFailedCount = "SUM(CASE " + MmsDatabase.MESSAGE_BOX + " " +
"WHEN " + MmsDatabase.Types.MESSAGE_BOX_SENT_FAILED + " THEN 1 " +
"ELSE 0 END)";
String smsGroupSentFailedCount = "SUM(CASE " + SmsDatabase.TYPE + " " +
"WHEN " + SmsDatabase.Types.FAILED_TYPE + " THEN 1 " +
"ELSE 0 END)";
String[] projection = {"_id", "body", "type", "address", "subject", "normalized_date AS date", "m_type", "msg_box", "transport_type", "COUNT(_id) AS group_size", mmsGroupSentCount + " AS mms_group_sent_count", mmsGroupSentFailedCount + " AS mms_group_sent_failed_count", smsGroupSentCount + " AS sms_group_sent_count", smsGroupSentFailedCount + " AS sms_group_sent_failed_count", smsCaseSecurity + " AS sms_collate", mmsCaseSecurity + " AS mms_collate"};
String order = "normalized_date ASC";
String selection = "thread_id = " + threadId;
String groupBy = "normalized_date / 1000, sms_collate, mms_collate";
Cursor cursor = queryTables(projection, selection, order, groupBy, null);
setNotifyConverationListeners(cursor, threadId);
return cursor;
}
public Cursor getConversation(long threadId) {
String[] projection = {"_id", "body", "type", "address", "subject", "normalized_date AS date", "m_type", "msg_box", "transport_type"};
String order = "normalized_date ASC";
String selection = "thread_id = " + threadId;
Cursor cursor = queryTables(projection, selection, order, null, null);
setNotifyConverationListeners(cursor, threadId);
return cursor;
}
public Cursor getConversationSnippet(long threadId) {
String[] projection = {"_id", "body", "type", "address", "subject", "normalized_date AS date", "m_type", "msg_box", "transport_type"};
String order = "normalized_date DESC";
String selection = "thread_id = " + threadId;
Cursor cursor = queryTables(projection, selection, order, "1");
Cursor cursor = queryTables(projection, selection, order, null, "1");
return cursor;
}
public Cursor getUnread() {
String[] projection = {"_id", "body", "read", "type", "address", "subject", "thread_id", "normalized_date AS date", "m_type", "msg_box", "transport_type"};
String order = "normalized_date ASC";
String selection = "read = 0";
Cursor cursor = queryTables(projection, selection, order, null);
return cursor;
Cursor cursor = queryTables(projection, selection, order, null, null);
return cursor;
}
public int getConversationCount(long threadId) {
int count = DatabaseFactory.getSmsDatabase(context).getMessageCountForThread(threadId);
count += DatabaseFactory.getMmsDatabase(context).getMessageCountForThread(threadId);
return count;
}
private Cursor queryTables(String[] projection, String selection, String order, String limit) {
private Cursor queryTables(String[] projection, String selection, String order, String groupBy, String limit) {
String[] mmsProjection = {"date * 1000 AS normalized_date", "_id", "body", "read", "thread_id", "type", "address", "subject", "date", "m_type", "msg_box", "transport_type"};
String[] smsProjection = {"date * 1 AS normalized_date", "_id", "body", "read", "thread_id", "type", "address", "subject", "date", "m_type", "msg_box", "transport_type"};
SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
@@ -82,7 +134,7 @@ public class MmsSmsDatabase extends Database {
mmsQueryBuilder.setTables(MmsDatabase.TABLE_NAME);
smsQueryBuilder.setTables(SmsDatabase.TABLE_NAME);
Set<String> mmsColumnsPresent = new HashSet<String>();
mmsColumnsPresent.add("_id");
mmsColumnsPresent.add("m_type");
@@ -90,7 +142,7 @@ public class MmsSmsDatabase extends Database {
mmsColumnsPresent.add("date");
mmsColumnsPresent.add("read");
mmsColumnsPresent.add("thread_id");
Set<String> smsColumnsPresent = new HashSet<String>();
smsColumnsPresent.add("_id");
smsColumnsPresent.add("body");
@@ -104,19 +156,19 @@ public class MmsSmsDatabase extends Database {
String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery("transport_type", mmsProjection, mmsColumnsPresent, 0, "mms", selection, null, null, null);
String smsSubQuery = smsQueryBuilder.buildUnionSubQuery("transport_type", smsProjection, smsColumnsPresent, 0, "sms", selection, null, null, null);
SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder();
SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder();
String unionQuery = unionQueryBuilder.buildUnionQuery(new String[] {smsSubQuery, mmsSubQuery}, order, null);
SQLiteQueryBuilder outerQueryBuilder = new SQLiteQueryBuilder();
outerQueryBuilder.setTables("(" + unionQuery + ")");
String query = outerQueryBuilder.buildQuery(projection, null, null, null, null, null, limit);
String query = outerQueryBuilder.buildQuery(projection, null, null, groupBy, null, null, limit);
Log.w("MmsSmsDatabase", "Executing query: " + query);
SQLiteDatabase db = databaseHelper.getReadableDatabase();
Cursor cursor = db.rawQuery(query, null);
return cursor;
}
}

View File

@@ -1,6 +1,6 @@
/**
/**
* 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
@@ -10,12 +10,28 @@
* 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.database;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import org.thoughtcrime.securesms.providers.PartProvider;
import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.CharacterSets;
import ws.com.google.android.mms.pdu.PduBody;
import ws.com.google.android.mms.pdu.PduPart;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
@@ -25,21 +41,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import org.thoughtcrime.securesms.providers.PartProvider;
import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.CharacterSets;
import ws.com.google.android.mms.pdu.PduBody;
import ws.com.google.android.mms.pdu.PduPart;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
public class PartDatabase extends Database {
private static final String TABLE_NAME = "part";
@@ -61,77 +62,81 @@ public class PartDatabase extends Database {
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ID + " INTEGER PRIMARY KEY, " +
MMS_ID + " INTEGER, " + SEQUENCE + " INTEGER DEFAULT 0, " +
CONTENT_TYPE + " TEXT, " + NAME + " TEXT, " + CHARSET + " INTEGER, " +
CONTENT_DISPOSITION + " TEXT, " + FILENAME + " TEXT, " + CONTENT_ID + " TEXT, " +
CONTENT_LOCATION + " TEXT, " + CONTENT_TYPE_START + " INTEGER, " +
CONTENT_DISPOSITION + " TEXT, " + FILENAME + " TEXT, " + CONTENT_ID + " TEXT, " +
CONTENT_LOCATION + " TEXT, " + CONTENT_TYPE_START + " INTEGER, " +
CONTENT_TYPE_TYPE + " TEXT, " + ENCRYPTED + " INTEGER, " + DATA + " TEXT);";
public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS part_mms_id_index ON " + TABLE_NAME + " (" + MMS_ID + ");"
};
public PartDatabase(Context context, SQLiteOpenHelper databaseHelper) {
super(context, databaseHelper);
}
private void getPartValues(PduPart part, Cursor cursor) {
int charsetColumn = cursor.getColumnIndexOrThrow(CHARSET);
if (!cursor.isNull(charsetColumn))
part.setCharset(cursor.getInt(charsetColumn));
int contentTypeColumn = cursor.getColumnIndexOrThrow(CONTENT_TYPE);
if (!cursor.isNull(contentTypeColumn))
part.setContentType(getBytes(cursor.getString(contentTypeColumn)));
int nameColumn = cursor.getColumnIndexOrThrow(NAME);
if (!cursor.isNull(nameColumn))
part.setName(getBytes(cursor.getString(nameColumn)));
int fileNameColumn = cursor.getColumnIndexOrThrow(FILENAME);
if (!cursor.isNull(fileNameColumn))
part.setFilename(getBytes(cursor.getString(fileNameColumn)));
int contentDispositionColumn = cursor.getColumnIndexOrThrow(CONTENT_DISPOSITION);
if (!cursor.isNull(contentDispositionColumn))
part.setContentDisposition(getBytes(cursor.getString(contentDispositionColumn)));
int contentIdColumn = cursor.getColumnIndexOrThrow(CONTENT_ID);
if (!cursor.isNull(contentIdColumn))
part.setContentId(getBytes(cursor.getString(contentIdColumn)));
int contentLocationColumn = cursor.getColumnIndexOrThrow(CONTENT_LOCATION);
if (!cursor.isNull(contentLocationColumn))
part.setContentLocation(getBytes(cursor.getString(contentLocationColumn)));
int encryptedColumn = cursor.getColumnIndexOrThrow(ENCRYPTED);
if (!cursor.isNull(encryptedColumn))
part.setEncrypted(cursor.getInt(encryptedColumn) == 1);
}
private ContentValues getContentValuesForPart(PduPart part) throws MmsException {
ContentValues contentValues = new ContentValues();
if (part.getCharset() != 0 ) {
contentValues.put(CHARSET, part.getCharset());
}
if (part.getContentType() != null) {
contentValues.put(CONTENT_TYPE, toIsoString(part.getContentType()));
if (toIsoString(part.getContentType()).equals(ContentType.APP_SMIL))
contentValues.put(SEQUENCE, -1);
contentValues.put(SEQUENCE, -1);
} else {
throw new MmsException("There is no content type for this part.");
}
if (part.getName() != null) {
contentValues.put(NAME, new String(part.getName()));
}
if (part.getFilename() != null) {
contentValues.put(FILENAME, new String(part.getFilename()));
}
@@ -147,33 +152,33 @@ public class PartDatabase extends Database {
if (part.getContentLocation() != null) {
contentValues.put(CONTENT_LOCATION, toIsoString(part.getContentLocation()));
}
contentValues.put(ENCRYPTED, part.getEncrypted() ? 1 : 0);
return contentValues;
}
protected FileInputStream getPartInputStream(File file, PduPart part) throws FileNotFoundException {
Log.w("PartDatabase", "Reading non-encrypted part from: " + file.getAbsolutePath());
return new FileInputStream(file);
}
protected FileOutputStream getPartOutputStream(File file, PduPart part) throws FileNotFoundException {
Log.w("PartDatabase", "Writing non-encrypted part to: " + file.getAbsolutePath());
return new FileOutputStream(file);
}
private void readPartData(PduPart part, String filename) {
try {
File dataFile = new File(filename);
FileInputStream fin = getPartInputStream(dataFile, part);
ByteArrayOutputStream baos = new ByteArrayOutputStream((int)dataFile.length());
ByteArrayOutputStream baos = new ByteArrayOutputStream((int)dataFile.length());
byte[] buffer = new byte[512];
int read;
while ((read = fin.read(buffer)) != -1)
baos.write(buffer, 0, read);
baos.write(buffer, 0, read);
part.setData(baos.toByteArray());
fin.close();
} catch (IOException ioe) {
@@ -181,7 +186,7 @@ public class PartDatabase extends Database {
part.setData(null);
}
}
private File writePartData(PduPart part) throws MmsException {
try {
File partsDirectory = context.getDir("parts", Context.MODE_PRIVATE);
@@ -189,23 +194,23 @@ public class PartDatabase extends Database {
FileOutputStream fout = getPartOutputStream(dataFile, part);
if (part.getData() != null) {
Log.w("PartDatabase", "Writing part data from buffer");
fout.write(part.getData());
fout.close();
return dataFile;
Log.w("PartDatabase", "Writing part data from buffer");
fout.write(part.getData());
fout.close();
return dataFile;
} else if (part.getDataUri() != null) {
Log.w("PartDatabase", "Writing part dat from URI");
byte[] buf = new byte[512];
InputStream in = context.getContentResolver().openInputStream(part.getDataUri());
int read;
while ((read = in.read(buf)) != -1)
fout.write(buf, 0, read);
fout.close();
in.close();
return dataFile;
Log.w("PartDatabase", "Writing part dat from URI");
byte[] buf = new byte[512];
InputStream in = context.getContentResolver().openInputStream(part.getDataUri());
int read;
while ((read = in.read(buf)) != -1)
fout.write(buf, 0, read);
fout.close();
in.close();
return dataFile;
} else {
throw new MmsException("Part is empty!");
throw new MmsException("Part is empty!");
}
} catch (FileNotFoundException e) {
throw new AssertionError(e);
@@ -213,7 +218,7 @@ public class PartDatabase extends Database {
throw new AssertionError(e);
}
}
private PduPart getPart(Cursor cursor, boolean includeData) {
PduPart part = new PduPart();
String dataLocation = cursor.getString(cursor.getColumnIndexOrThrow(DATA));
@@ -223,119 +228,119 @@ public class PartDatabase extends Database {
if (includeData)
readPartData(part, dataLocation);
part.setDataUri(ContentUris.withAppendedId(PartProvider.CONTENT_URI, partId));
return part;
}
private long insertPart(PduPart part, long mmsId) throws MmsException {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
File dataFile = writePartData(part);
Log.w("PartDatabase", "Wrote part to file: " + dataFile.getAbsolutePath());
ContentValues contentValues = getContentValuesForPart(part);
contentValues.put(MMS_ID, mmsId);
contentValues.put(DATA, dataFile.getAbsolutePath());
return database.insert(TABLE_NAME, null, contentValues);
return database.insert(TABLE_NAME, null, contentValues);
}
public InputStream getPartStream(long partId) throws FileNotFoundException {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = null;
Log.w("PartDatabase", "Getting part at ID: " + partId);
try {
cursor = database.query(TABLE_NAME, new String[]{DATA, ENCRYPTED}, ID_WHERE, new String[] {partId+""}, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
PduPart part = new PduPart();
part.setEncrypted(cursor.getInt(1) == 1);
return getPartInputStream(new File(cursor.getString(0)), part);
PduPart part = new PduPart();
part.setEncrypted(cursor.getInt(1) == 1);
return getPartInputStream(new File(cursor.getString(0)), part);
} else {
throw new FileNotFoundException("No part for id: " + partId);
throw new FileNotFoundException("No part for id: " + partId);
}
} finally {
if (cursor != null)
cursor.close();
}
cursor.close();
}
}
public void insertParts(long mmsId, PduBody body) throws MmsException {
public void insertParts(long mmsId, PduBody body) throws MmsException {
for (int i=0;i<body.getPartsNum();i++) {
long partId = insertPart(body.getPart(i), mmsId);
Log.w("PartDatabase", "Inserted part at ID: " + partId);
}
}
}
}
public PduPart getPart(long partId, boolean includeData) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = null;
try {
cursor = database.query(TABLE_NAME, null, ID_WHERE, new String[] {partId+""}, null, null, null);
if (cursor != null && cursor.moveToFirst())
return getPart(cursor, includeData);
return getPart(cursor, includeData);
else
return null;
return null;
} finally {
if (cursor != null)
cursor.close();
}
cursor.close();
}
}
public PduBody getParts(long mmsId, boolean includeData) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
PduBody body = new PduBody();
Cursor cursor = null;
try {
cursor = database.query(TABLE_NAME, null, MMS_ID + " = ?", new String[] {mmsId+""}, null, null, null);
while (cursor != null && cursor.moveToNext()) {
PduPart part = getPart(cursor, includeData);
body.addPart(part);
PduPart part = getPart(cursor, includeData);
body.addPart(part);
}
return body;
} finally {
if (cursor != null)
cursor.close();
cursor.close();
}
}
public void deleteParts(long mmsId) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
Cursor cursor = null;
try {
cursor = database.query(TABLE_NAME, new String[] {DATA}, MMS_ID + " = ?", new String[] {mmsId+""}, null, null, null);
while (cursor != null && cursor.moveToNext()) {
new File(cursor.getString(0)).delete();
new File(cursor.getString(0)).delete();
}
} finally {
if (cursor != null)
cursor.close();
cursor.close();
}
database.delete(TABLE_NAME, MMS_ID + " = ?", new String[] {mmsId+""});
}
public void deleteAllParts() {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
database.delete(TABLE_NAME, null, null);
File partsDirectory = context.getDir("parts", Context.MODE_PRIVATE);
File[] parts = partsDirectory.listFiles();
for (int i=0;i<parts.length;i++) {
parts[i].delete();
}
}
private byte[] getBytes(String data) {
try {
return data.getBytes(CharacterSets.MIMENAME_ISO_8859_1);
@@ -344,7 +349,7 @@ public class PartDatabase extends Database {
return new byte[0];
}
}
private String toIsoString(byte[] bytes) {
try {
return new String(bytes, CharacterSets.MIMENAME_ISO_8859_1);
@@ -354,7 +359,4 @@ public class PartDatabase extends Database {
return "";
}
}
}

View File

@@ -1,6 +1,6 @@
/**
/**
* 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
@@ -10,49 +10,50 @@
* 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.database;
import java.io.UnsupportedEncodingException;
import android.database.Cursor;
import android.util.Log;
import ws.com.google.android.mms.InvalidHeaderValueException;
import ws.com.google.android.mms.pdu.CharacterSets;
import ws.com.google.android.mms.pdu.EncodedStringValue;
import ws.com.google.android.mms.pdu.PduHeaders;
import android.database.Cursor;
import android.util.Log;
import java.io.UnsupportedEncodingException;
public class PduHeadersBuilder {
private final PduHeaders headers;
private final Cursor cursor;
public PduHeadersBuilder(PduHeaders headers, Cursor cursor) {
this.headers = headers;
this.cursor = cursor;
}
public PduHeaders getHeaders() {
return headers;
}
public void addLong(String key, int headersKey) {
int columnIndex = cursor.getColumnIndexOrThrow(key);
if (!cursor.isNull(columnIndex))
headers.setLongInteger(cursor.getLong(columnIndex), headersKey);
}
public void addOctet(String key, int headersKey) throws InvalidHeaderValueException {
int columnIndex = cursor.getColumnIndexOrThrow(key);
if (!cursor.isNull(columnIndex))
headers.setOctet(cursor.getInt(columnIndex), headersKey);
}
public void addText(String key, int headersKey) {
String value = cursor.getString(cursor.getColumnIndexOrThrow(key));
if (value != null && value.trim().length() > 0)
@@ -60,14 +61,14 @@ public class PduHeadersBuilder {
}
public void add(String key, String charsetKey, int headersKey) {
String value = cursor.getString(cursor.getColumnIndexOrThrow(key));
if (value != null && value.trim().length() > 0) {
int charsetValue = cursor.getInt(cursor.getColumnIndexOrThrow(charsetKey));
EncodedStringValue encodedValue = new EncodedStringValue(charsetValue, getBytes(value));
headers.setEncodedStringValue(encodedValue, headersKey);
}
}
private byte[] getBytes(String data) {
try {
return data.getBytes(CharacterSets.MIMENAME_ISO_8859_1);

View File

@@ -1,6 +1,6 @@
/**
/**
* 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
@@ -10,12 +10,16 @@
* 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.database;
import android.content.Context;
import org.thoughtcrime.securesms.util.Conversions;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -24,24 +28,20 @@ import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import org.thoughtcrime.securesms.util.Conversions;
import android.content.Context;
public abstract class Record {
protected final String address;
protected final Context context;
public Record(Context context, String address) {
this.context = context;
this.address = address;
}
}
public void delete() {
delete(this.context, this.address);
}
protected static void delete(Context context, String address) {
getAddressFile(context, address).delete();
}
@@ -52,44 +52,44 @@ public abstract class Record {
protected RandomAccessFile openRandomAccessFile() throws FileNotFoundException {
return new RandomAccessFile(getAddressFile(), "rw");
}
}
protected FileInputStream openInputStream() throws FileNotFoundException {
return new FileInputStream(getAddressFile().getAbsolutePath());
}
private File getAddressFile() {
return getAddressFile(context, address);
}
private static File getAddressFile(Context context, String address) {
return new File(context.getFilesDir().getAbsolutePath() + File.separatorChar + "sessions", address);
return new File(context.getFilesDir().getAbsolutePath() + File.separatorChar + "sessions", address);
}
protected byte[] readBlob(FileInputStream in) throws IOException {
int length = readInteger(in);
byte[] blobBytes = new byte[length];
in.read(blobBytes, 0, blobBytes.length);
return blobBytes;
}
protected void writeBlob(byte[] blobBytes, FileChannel out) throws IOException {
writeInteger(blobBytes.length, out);
ByteBuffer buffer = ByteBuffer.wrap(blobBytes);
out.write(buffer);
}
protected int readInteger(FileInputStream in) throws IOException {
byte[] integer = new byte[4];
in.read(integer, 0, integer.length);
return Conversions.byteArrayToInt(integer);
}
protected void writeInteger(int value, FileChannel out) throws IOException {
byte[] valueBytes = Conversions.intToByteArray(value);
ByteBuffer buffer = ByteBuffer.wrap(valueBytes);
out.write(buffer);
}
}

View File

@@ -1,6 +1,6 @@
/**
/**
* 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
@@ -10,57 +10,57 @@
* 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.database;
import android.content.Context;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.InvalidKeyException;
import org.thoughtcrime.securesms.crypto.PublicKey;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.Hex;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import org.thoughtcrime.securesms.crypto.InvalidKeyException;
import org.thoughtcrime.securesms.crypto.PublicKey;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.Hex;
import android.content.Context;
import android.util.Log;
/**
* Represents the current and last public key belonging to the "remote"
* endpoint in an encrypted session. These are stored on disk.
*
*
* @author Moxie Marlinspike
*/
public class RemoteKeyRecord extends Record {
public class RemoteKeyRecord extends Record {
private static final Object FILE_LOCK = new Object();
private PublicKey remoteKeyCurrent;
private PublicKey remoteKeyLast;
public RemoteKeyRecord(Context context, Recipient recipient) {
super(context,getFileNameForRecipient(context, recipient));
loadData();
}
public static void delete(Context context, Recipient recipient) {
Record.delete(context, getFileNameForRecipient(context, recipient));
}
public static boolean hasRecord(Context context, Recipient recipient) {
Log.w("LocalKeyRecord", "Checking: " + getFileNameForRecipient(context, recipient));
return Record.hasRecord(context, getFileNameForRecipient(context, recipient));
}
private static String getFileNameForRecipient(Context context, Recipient recipient) {
return CanonicalAddressDatabase.getInstance(context).getCanonicalAddress(recipient.getNumber()) + "-remote";
}
public void updateCurrentRemoteKey(PublicKey remoteKey) {
Log.w("RemoteKeyRecord", "Updating current remote key: " + remoteKey.getId());
if (remoteKey.getId() > remoteKeyCurrent.getId()) {
@@ -68,29 +68,29 @@ public class RemoteKeyRecord extends Record {
this.remoteKeyCurrent = remoteKey;
}
}
public void setCurrentRemoteKey(PublicKey remoteKeyCurrent) {
this.remoteKeyCurrent = remoteKeyCurrent;
}
public void setLastRemoteKey(PublicKey remoteKeyLast) {
this.remoteKeyLast = remoteKeyLast;
}
public PublicKey getCurrentRemoteKey() {
return this.remoteKeyCurrent;
}
public PublicKey getLastRemoteKey() {
return this.remoteKeyLast;
}
public PublicKey getKeyForId(int id) throws InvalidKeyIdException {
if (this.remoteKeyCurrent.getId() == id) return this.remoteKeyCurrent;
else if (this.remoteKeyLast.getId() == id) return this.remoteKeyLast;
else throw new InvalidKeyIdException("No remote key for ID: " + id);
}
public void save() {
Log.w("RemoteKeyRecord", "Saving remote key record for recipient: " + this.address);
synchronized (FILE_LOCK) {
@@ -99,10 +99,10 @@ public class RemoteKeyRecord extends Record {
FileChannel out = file.getChannel();
Log.w("RemoteKeyRecord", "Opened file of size: " + out.size());
out.position(0);
writeKey(remoteKeyCurrent, out);
writeKey(remoteKeyLast, out);
out.truncate(out.position());
out.close();
file.close();
@@ -112,12 +112,12 @@ public class RemoteKeyRecord extends Record {
}
}
}
private void loadData() {
Log.w("RemoteKeyRecord", "Loading remote key record for recipient: " + this.address);
synchronized (FILE_LOCK) {
try {
FileInputStream in = this.openInputStream();
FileInputStream in = this.openInputStream();
remoteKeyCurrent = readKey(in);
remoteKeyLast = readKey(in);
in.close();
@@ -130,13 +130,13 @@ public class RemoteKeyRecord extends Record {
}
}
}
private void writeKey(PublicKey key, FileChannel out) throws IOException {
byte[] keyBytes = key.serialize();
Log.w("RemoteKeyRecord", "Serializing remote key bytes: " + Hex.toString(keyBytes));
writeBlob(keyBytes, out);
}
private PublicKey readKey(FileInputStream in) throws IOException {
try {
byte[] keyBytes = readBlob(in);

View File

@@ -1,6 +1,6 @@
/**
/**
* 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
@@ -10,25 +10,25 @@
* 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.database;
import javax.crypto.spec.SecretKeySpec;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.SessionCipher;
import org.thoughtcrime.securesms.util.Conversions;
import org.thoughtcrime.securesms.util.Util;
import javax.crypto.spec.SecretKeySpec;
/**
* Represents the currently negotiated session key for a given
* local key id and remote key id. This is stored encrypted on
* disk.
*
*
* @author Moxie Marlinspike
*/
@@ -39,7 +39,7 @@ public class SessionKey {
private SecretKeySpec cipherKey;
private SecretKeySpec macKey;
private MasterCipher masterCipher;
public SessionKey(int localKeyId, int remoteKeyId, SecretKeySpec cipherKey, SecretKeySpec macKey, MasterSecret masterSecret) {
this.localKeyId = localKeyId;
this.remoteKeyId = remoteKeyId;
@@ -47,33 +47,33 @@ public class SessionKey {
this.macKey = macKey;
this.masterCipher = new MasterCipher(masterSecret);
}
public SessionKey(byte[] bytes, MasterSecret masterSecret) {
this.masterCipher = new MasterCipher(masterSecret);
deserialize(bytes);
}
public byte[] serialize() {
byte[] localKeyIdBytes = Conversions.mediumToByteArray(localKeyId);
byte[] remoteKeyIdBytes = Conversions.mediumToByteArray(remoteKeyId);
byte[] cipherKeyBytes = cipherKey.getEncoded();
byte[] macKeyBytes = macKey.getEncoded();
byte[] combined = Util.combine(localKeyIdBytes, remoteKeyIdBytes, cipherKeyBytes, macKeyBytes);
return masterCipher.encryptBytes(combined);
}
private void deserialize(byte[] bytes) {
byte[] decrypted = masterCipher.encryptBytes(bytes);
this.localKeyId = Conversions.byteArrayToMedium(decrypted, 0);
this.remoteKeyId = Conversions.byteArrayToMedium(decrypted, 3);
byte[] keyBytes = new byte[SessionCipher.CIPHER_KEY_LENGTH];
System.arraycopy(decrypted, 6, keyBytes, 0, keyBytes.length);
byte[] macBytes = new byte[SessionCipher.MAC_KEY_LENGTH];
System.arraycopy(decrypted, 6 + keyBytes.length, macBytes, 0, macBytes.length);
this.cipherKey = new SecretKeySpec(keyBytes, "AES");
this.macKey = new SecretKeySpec(macBytes, "HmacSHA1");
}
@@ -93,5 +93,5 @@ public class SessionKey {
public SecretKeySpec getMacKey() {
return this.macKey;
}
}

View File

@@ -208,8 +208,8 @@ public class SessionRecord extends Record {
Log.w("SessionRecord", "No session information found.");
return;
} catch (IOException ioe) {
Log.w("keyrecord", ioe);
// XXX
Log.w("keyrecord", ioe);
// XXX
}
}
}
@@ -223,5 +223,4 @@ public class SessionRecord extends Record {
return null;
}
}

View File

@@ -39,7 +39,6 @@ import java.util.Set;
*/
public class SmsDatabase extends Database {
public static final String TRANSPORT = "transport_type";
public static final String TABLE_NAME = "sms";
public static final String ID = "_id";
@@ -62,6 +61,13 @@ public class SmsDatabase extends Database {
TYPE + " INTEGER, " + REPLY_PATH_PRESENT + " INTEGER, " + SUBJECT + " TEXT, " + BODY + " TEXT, " +
SERVICE_CENTER + " TEXT);";
public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS sms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");",
"CREATE INDEX IF NOT EXISTS sms_read_index ON " + TABLE_NAME + " (" + READ + ");",
"CREATE INDEX IF NOT EXISTS sms_read_and_thread_id_index ON " + TABLE_NAME + "(" + READ + "," + THREAD_ID + ");",
"CREATE INDEX IF NOT EXISTS sms_type_index ON " + TABLE_NAME + " (" + TYPE + ");"
};
public SmsDatabase(Context context, SQLiteOpenHelper databaseHelper) {
super(context, databaseHelper);
}
@@ -117,12 +123,12 @@ public class SmsDatabase extends Database {
try {
cursor = db.rawQuery(sql, sqlArgs);
if (cursor != null && cursor.moveToFirst())
return cursor.getLong(0);
return cursor.getLong(0);
else
return -1;
return -1;
} finally {
if (cursor != null)
cursor.close();
cursor.close();
}
}
@@ -134,10 +140,10 @@ public class SmsDatabase extends Database {
cursor = db.query(TABLE_NAME, new String[] {"COUNT(*)"}, THREAD_ID + " = ?", new String[] {threadId+""}, null, null, null);
if (cursor != null && cursor.moveToFirst())
return cursor.getInt(0);
return cursor.getInt(0);
} finally {
if (cursor != null)
cursor.close();
cursor.close();
}
return 0;
@@ -201,7 +207,7 @@ public class SmsDatabase extends Database {
public long insertMessageSent(String address, long threadId, String body, long date, long type) {
ContentValues contentValues = new ContentValues(6);
// contentValues.put(ADDRESS, NumberUtil.filterNumber(address));
// contentValues.put(ADDRESS, NumberUtil.filterNumber(address));
contentValues.put(ADDRESS, address);
contentValues.put(THREAD_ID, threadId);
contentValues.put(BODY, body);
@@ -292,7 +298,7 @@ public class SmsDatabase extends Database {
/*package*/ SQLiteStatement createInsertStatement(SQLiteDatabase database) {
return database.compileStatement("INSERT INTO " + TABLE_NAME + " (" + ADDRESS + ", " + PERSON + ", " + DATE + ", " + PROTOCOL + ", " + READ + ", " + STATUS + ", " + TYPE + ", " + REPLY_PATH_PRESENT + ", " + SUBJECT + ", " + BODY + ", " + SERVICE_CENTER + ", THREAD_ID) " +
" VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
" VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
}
public static class Types {

View File

@@ -50,17 +50,20 @@ public class ThreadDatabase extends Database {
READ + " INTEGER DEFAULT 1, " + TYPE + " INTEGER DEFAULT 0, " + ERROR + " INTEGER DEFAULT 0, " +
HAS_ATTACHMENT + " INTEGER DEFAULT 0);";
public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS thread_recipient_ids_index ON " + TABLE_NAME + " (" + RECIPIENT_IDS + ");",
};
public ThreadDatabase(Context context, SQLiteOpenHelper databaseHelper) {
super(context, databaseHelper);
}
private long[] getRecipientIds(Recipients recipients) {
Set<Long> recipientSet = new HashSet<Long>();
List<Recipient> recipientList = recipients.getRecipientsList();
for (Recipient recipient : recipientList) {
// String number = NumberUtil.filterNumber(recipient.getNumber());
// String number = NumberUtil.filterNumber(recipient.getNumber());
String number = recipient.getNumber();
recipientSet.add(Long.valueOf(DatabaseFactory.getAddressDatabase(context).getCanonicalAddress(number)));
}
@@ -229,12 +232,12 @@ public class ThreadDatabase extends Database {
cursor = db.query(TABLE_NAME, new String[]{ID}, where, recipientsArg, null, null, null);
if (cursor != null && cursor.moveToFirst())
return cursor.getLong(cursor.getColumnIndexOrThrow(ID));
return cursor.getLong(cursor.getColumnIndexOrThrow(ID));
else
return -1L;
return -1L;
} finally {
if (cursor != null)
cursor.close();
cursor.close();
}
}
@@ -250,12 +253,12 @@ public class ThreadDatabase extends Database {
cursor = db.query(TABLE_NAME, new String[]{ID}, where, recipientsArg, null, null, null);
if (cursor != null && cursor.moveToFirst())
return cursor.getLong(cursor.getColumnIndexOrThrow(ID));
return cursor.getLong(cursor.getColumnIndexOrThrow(ID));
else
return createThreadForRecipients(recipientsList, recipientIds.length);
return createThreadForRecipients(recipientsList, recipientIds.length);
} finally {
if (cursor != null)
cursor.close();
cursor.close();
}
}
@@ -273,22 +276,18 @@ public class ThreadDatabase extends Database {
try {
cursor = mmsSmsDatabase.getConversationSnippet(threadId);
if (cursor != null && cursor.moveToFirst())
updateThread(threadId, count,
cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.BODY)),
cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.DATE)));
else
deleteThread(threadId);
if (cursor != null && cursor.moveToFirst()) {
updateThread(threadId, count,
cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.BODY)),
cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.DATE)));
} else {
deleteThread(threadId);
}
} finally {
if (cursor != null)
cursor.close();
cursor.close();
}
notifyConversationListListeners();
}
}

View File

@@ -7,17 +7,24 @@ import android.support.v4.content.CursorLoader;
import org.thoughtcrime.securesms.database.DatabaseFactory;
public class ConversationLoader extends CursorLoader {
private final Context context;
private final long threadId;
private final boolean isGroupConversation;
public ConversationLoader(Context context, long threadId) {
public ConversationLoader(Context context, long threadId, boolean isGroupConversation) {
super(context);
this.context = context.getApplicationContext();
this.threadId = threadId;
this.context = context.getApplicationContext();
this.threadId = threadId;
this.isGroupConversation = isGroupConversation;
}
@Override
public Cursor loadInBackground() {
return DatabaseFactory.getMmsSmsDatabase(context).getConversation(threadId);
if (!isGroupConversation) {
return DatabaseFactory.getMmsSmsDatabase(context).getConversation(threadId);
} else {
return DatabaseFactory.getMmsSmsDatabase(context).getCollatedGroupConversation(threadId);
}
}
}

View File

@@ -0,0 +1,96 @@
/**
* Copyright (C) 2012 Moxie Marlinspike
*
* 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.database.model;
import org.thoughtcrime.securesms.protocol.Prefix;
import org.thoughtcrime.securesms.recipients.Recipients;
/**
* The base class for all message record models. Encapsulates basic data
* shared between ThreadRecord and MessageRecord.
*
* @author Moxie Marlinspike
*
*/
public abstract class DisplayRecord {
private final Recipients recipients;
private final long date;
private final long threadId;
private String body;
protected boolean emphasis;
protected boolean keyExchange;
protected boolean processedKeyExchange;
protected boolean staleKeyExchange;
public DisplayRecord(Recipients recipients, long date, long threadId) {
this.threadId = threadId;
this.recipients = recipients;
this.date = date;
this.emphasis = false;
}
public void setEmphasis(boolean emphasis) {
this.emphasis = emphasis;
}
public boolean getEmphasis() {
return emphasis;
}
public void setBody(String body) {
if (body.startsWith(Prefix.KEY_EXCHANGE)) {
this.keyExchange = true;
this.emphasis = true;
this.body = body;
} else if (body.startsWith(Prefix.PROCESSED_KEY_EXCHANGE)) {
this.processedKeyExchange = true;
this.emphasis = true;
this.body = body;
} else if (body.startsWith(Prefix.STALE_KEY_EXCHANGE)) {
this.staleKeyExchange = true;
this.emphasis = true;
this.body = body;
} else {
this.body = body;
this.emphasis = false;
}
}
public String getBody() {
return body;
}
public Recipients getRecipients() {
return recipients;
}
public long getDate() {
return date;
}
public long getThreadId() {
return threadId;
}
public boolean isKeyExchange() {
return keyExchange || processedKeyExchange || staleKeyExchange;
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (C) 2011 Whisper Systems
* Copyright (C) 2012 Moxie Marlinspike
*
* 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
@@ -14,56 +14,44 @@
* 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.database;
package org.thoughtcrime.securesms.database.model;
import android.content.Context;
import org.thoughtcrime.securesms.ConversationItem;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
import java.util.Iterator;
import java.util.List;
public class MmsMessageRecord extends MessageRecord {
/**
* Represents the message record model for MMS messages that contain
* media (ie: they've been downloaded).
*
* @author Moxie Marlinspike
*
*/
private SlideDeck slideDeck;
private byte[] contentLocation;
private long messageSize;
private long expiry;
private boolean isNotification;
private long mailbox;
private int status;
private byte[] transactionId;
public class MediaMmsMessageRecord extends MessageRecord {
public MmsMessageRecord(Context context, MessageRecord record, SlideDeck slideDeck, long mailbox) {
super(record);
this.slideDeck = slideDeck;
this.isNotification = false;
this.mailbox = mailbox;
private final SlideDeck slideDeck;
private final long mailbox;
public MediaMmsMessageRecord(Context context, long id, Recipients recipients,
Recipient individualRecipient, long date, long threadId,
SlideDeck slideDeck, long mailbox, GroupData groupData)
{
super(id, recipients, individualRecipient, date, threadId, groupData);
this.slideDeck = slideDeck;
this.mailbox = mailbox;
setBodyIfTextAvailable(context);
}
public MmsMessageRecord(MessageRecord record, byte[] contentLocation, long messageSize, long expiry, int status, byte[] transactionId) {
super(record);
this.contentLocation = contentLocation;
this.messageSize = messageSize;
this.expiry = expiry;
this.isNotification = true;
this.status = status;
this.transactionId = transactionId;
}
public byte[] getTransactionId() {
return transactionId;
}
public int getStatus() {
return this.status;
}
@Override
public boolean isOutgoing() {
return MmsDatabase.Types.isOutgoingMmsBox(mailbox);
@@ -84,43 +72,13 @@ public class MmsMessageRecord extends MessageRecord {
return MmsDatabase.Types.isSecureMmsBox(mailbox);
}
// This is the double-dispatch pattern, don't refactor
// this into the base class.
@Override
public void setOnConversationItem(ConversationItem item) {
item.setMessageRecord(this);
}
public byte[] getContentLocation() {
return contentLocation;
}
public long getMessageSize() {
return (messageSize + 1023) / 1024;
}
public long getExpiration() {
return expiry * 1000;
}
public boolean isNotification() {
return isNotification;
}
public SlideDeck getSlideDeck() {
return slideDeck;
}
private void setBodyFromSlidesIfTextAvailable() {
List<Slide> slides = slideDeck.getSlides();
Iterator<Slide> i = slides.iterator();
while (i.hasNext()) {
Slide slide = i.next();
if (slide.hasText())
setBody(slide.getText());
}
@Override
public boolean isMms() {
return true;
}
private void setBodyIfTextAvailable(Context context) {
@@ -135,7 +93,7 @@ public class MmsMessageRecord extends MessageRecord {
return;
case MmsDatabase.Types.MESSAGE_BOX_NO_SESSION_INBOX:
setBody(context
.getString(R.string.MmsMessageRecord_mms_message_encrypted_for_non_existing_session));
.getString(R.string.MmsMessageRecord_mms_message_encrypted_for_non_existing_session));
setEmphasis(true);
return;
}
@@ -143,9 +101,16 @@ public class MmsMessageRecord extends MessageRecord {
setBodyFromSlidesIfTextAvailable();
}
@Override
public boolean isMms() {
return true;
private void setBodyFromSlidesIfTextAvailable() {
List<Slide> slides = slideDeck.getSlides();
Iterator<Slide> i = slides.iterator();
while (i.hasNext()) {
Slide slide = i.next();
if (slide.hasText())
setBody(slide.getText());
}
}
}

View File

@@ -0,0 +1,98 @@
/**
* Copyright (C) 2012 Moxie Marlinpsike
*
* 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.database.model;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
/**
* The base class for message record models that are displayed in
* conversations, as opposed to models that are displayed in a thread list.
* Encapsulates the shared data between both SMS and MMS messages.
*
* @author Moxie Marlinspike
*
*/
public abstract class MessageRecord extends DisplayRecord {
private final Recipient individualRecipient;
private final long id;
private final GroupData groupData;
public MessageRecord(long id, Recipients recipients,
Recipient individualRecipient,
long date, long threadId,
GroupData groupData)
{
super(recipients, date, threadId);
this.id = id;
this.individualRecipient = individualRecipient;
this.groupData = groupData;
}
public abstract boolean isOutgoing();
public abstract boolean isFailed();
public abstract boolean isSecure();
public abstract boolean isPending();
public abstract boolean isMms();
public long getId() {
return id;
}
public boolean isStaleKeyExchange() {
return this.staleKeyExchange;
}
public boolean isProcessedKeyExchange() {
return this.processedKeyExchange;
}
public Recipient getIndividualRecipient() {
return individualRecipient;
}
public GroupData getGroupData() {
return this.groupData;
}
protected boolean isGroupDeliveryPending() {
if (this.groupData != null) {
return groupData.groupSentCount + groupData.groupSendFailedCount < groupData.groupSize;
}
return false;
}
public static class GroupData {
public final int groupSize;
public final int groupSentCount;
public final int groupSendFailedCount;
public GroupData(int groupSize, int groupSentCount, int groupSendFailedCount) {
this.groupSize = groupSize;
this.groupSentCount = groupSentCount;
this.groupSendFailedCount = groupSendFailedCount;
}
}
}

View File

@@ -0,0 +1,101 @@
/**
* Copyright (C) 2012 Moxie Marlinspike
*
* 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.database.model;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
/**
* Represents the message record model for MMS messages that are
* notifications (ie: they're pointers to undownloaded media).
*
* @author Moxie Marlinspike
*
*/
public class NotificationMmsMessageRecord extends MessageRecord {
private final byte[] contentLocation;
private final long messageSize;
private final long expiry;
private final int status;
private final byte[] transactionId;
public NotificationMmsMessageRecord(long id, Recipients recipients, Recipient individualRecipient,
long date, long threadId, byte[] contentLocation,
long messageSize, long expiry,
int status, byte[] transactionId)
{
super(id, recipients, individualRecipient, date, threadId, null);
this.contentLocation = contentLocation;
this.messageSize = messageSize;
this.expiry = expiry;
this.status = status;
this.transactionId = transactionId;
setBody("Multimedia Message");
setEmphasis(true);
}
public byte[] getTransactionId() {
return transactionId;
}
public int getStatus() {
return this.status;
}
public byte[] getContentLocation() {
return contentLocation;
}
public long getMessageSize() {
return (messageSize + 1023) / 1024;
}
public long getExpiration() {
return expiry * 1000;
}
@Override
public boolean isOutgoing() {
return false;
}
@Override
public boolean isFailed() {
return MmsDatabase.Types.isHardError(status);
}
@Override
public boolean isSecure() {
return false;
}
@Override
public boolean isPending() {
return false;
}
@Override
public boolean isMms() {
return true;
}
}

View File

@@ -0,0 +1,99 @@
/**
* Copyright (C) 2012 Moxie Marlinspike
*
* 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.database.model;
import android.content.Context;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.protocol.Prefix;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
/**
* The message record model which represents standard SMS messages.
*
* @author Moxie Marlinspike
*
*/
public class SmsMessageRecord extends MessageRecord {
private final Context context;
private final long type;
public SmsMessageRecord(Context context, long id,
Recipients recipients,
Recipient individualRecipient,
long date, long type, long threadId,
GroupData groupData)
{
super(id, recipients, individualRecipient, date, threadId, groupData);
this.context = context.getApplicationContext();
this.type = type;
}
public long getType() {
return type;
}
@Override
public void setBody(String body) {
if (this.type == SmsDatabase.Types.FAILED_DECRYPT_TYPE) {
super.setBody(context.getString(R.string.MessageDisplayHelper_bad_encrypted_message));
setEmphasis(true);
} else if (this.type == SmsDatabase.Types.DECRYPT_IN_PROGRESS_TYPE ||
(type == 0 && body.startsWith(Prefix.ASYMMETRIC_ENCRYPT)) ||
(type == 0 && body.startsWith(Prefix.ASYMMETRIC_LOCAL_ENCRYPT)))
{
super.setBody(context.getString(R.string.MessageDisplayHelper_decrypting_please_wait));
setEmphasis(true);
} else if (type == SmsDatabase.Types.NO_SESSION_TYPE) {
super.setBody(context.getString(R.string.MessageDisplayHelper_message_encrypted_for_non_existing_session));
setEmphasis(true);
} else {
super.setBody(body);
}
}
@Override
public boolean isFailed() {
return SmsDatabase.Types.isFailedMessageType(getType());
}
@Override
public boolean isOutgoing() {
return SmsDatabase.Types.isOutgoingMessageType(getType());
}
@Override
public boolean isPending() {
return SmsDatabase.Types.isPendingMessageType(getType()) || isGroupDeliveryPending();
}
@Override
public boolean isSecure() {
return SmsDatabase.Types.isSecureType(getType());
}
@Override
public boolean isMms() {
return false;
}
}

View File

@@ -0,0 +1,71 @@
/**
* Copyright (C) 2012 Moxie Marlinspike
*
* 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.database.model;
import android.content.Context;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.protocol.Prefix;
import org.thoughtcrime.securesms.recipients.Recipients;
/**
* The message record model which represents thread heading messages.
*
* @author Moxie Marlinspike
*
*/
public class ThreadRecord extends DisplayRecord {
private final Context context;
private final long count;
private final boolean read;
public ThreadRecord(Context context, Recipients recipients,
long date, long count,
boolean read, long threadId)
{
super(recipients, date, threadId);
this.context = context.getApplicationContext();
this.count = count;
this.read = read;
}
@Override
public void setBody(String body) {
if (body.startsWith(Prefix.SYMMETRIC_ENCRYPT) ||
body.startsWith(Prefix.ASYMMETRIC_ENCRYPT) ||
body.startsWith(Prefix.ASYMMETRIC_LOCAL_ENCRYPT))
{
super.setBody(context.getString(R.string.ConversationListAdapter_encrypted_message_enter_passphrase));
setEmphasis(true);
} else if (body.startsWith(Prefix.KEY_EXCHANGE)) {
super.setBody(context.getString(R.string.ConversationListAdapter_key_exchange_message));
setEmphasis(true);
} else {
super.setBody(body);
}
}
public long getCount() {
return count;
}
public boolean isRead() {
return read;
}
}

View File

@@ -17,11 +17,6 @@
package org.thoughtcrime.securesms.mms;
import java.util.ArrayList;
import java.util.List;
import org.thoughtcrime.securesms.R;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
@@ -30,6 +25,11 @@ import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import org.thoughtcrime.securesms.R;
import java.util.ArrayList;
import java.util.List;
public class AttachmentTypeSelectorAdapter extends ArrayAdapter<AttachmentTypeSelectorAdapter.IconListItem> {
public static final int ADD_IMAGE = 1;
@@ -38,78 +38,80 @@ public class AttachmentTypeSelectorAdapter extends ArrayAdapter<AttachmentTypeSe
// public static final int RECORD_VIDEO = 4;
public static final int ADD_SOUND = 5;
// public static final int RECORD_SOUND = 6;
private final Context context;
public AttachmentTypeSelectorAdapter(Context context) {
super(context, R.layout.icon_list_item, getItemList());
super(context, R.layout.icon_list_item, getItemList(context));
this.context = context;
}
public int buttonToCommand(int position) {
return getItem(position).getCommand();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView text;
ImageView image;
View view;
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.icon_list_item, parent, false);
} else {
view = convertView;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView text;
ImageView image;
text = (TextView) view.findViewById(R.id.text1);
text.setText(getItem(position).getTitle());
View view;
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.icon_list_item, parent, false);
} else {
view = convertView;
}
image = (ImageView) view.findViewById(R.id.icon);
image.setImageResource(getItem(position).getResource());
text = (TextView) view.findViewById(R.id.text1);
text.setText(getItem(position).getTitle());
return view;
}
private static List<IconListItem> getItemList() {
List<IconListItem> data = new ArrayList<IconListItem>(7);
addItem(data, "Pictures", R.drawable.ic_launcher_gallery, ADD_IMAGE);
image = (ImageView) view.findViewById(R.id.icon);
image.setImageResource(getItem(position).getResource());
return view;
}
private static List<IconListItem> getItemList(Context context) {
List<IconListItem> data = new ArrayList<IconListItem>(7);
addItem(data, context.getString(R.string.AttachmentTypeSelectorAdapter_picture),
R.drawable.ic_attach_picture_holo_light, ADD_IMAGE);
// addItem(data, "Capture picture", R.drawable.ic_launcher_camera, TAKE_PICTURE);
addItem(data, "Videos", R.drawable.ic_launcher_video_player, ADD_VIDEO);
addItem(data, context.getString(R.string.AttachmentTypeSelectorAdapter_video),
R.drawable.ic_attach_video_holo_light, ADD_VIDEO);
// addItem(data, "Capture video", R.drawable.ic_launcher_camera_record, RECORD_VIDEO);
addItem(data, "Audio", R.drawable.ic_launcher_musicplayer_2, ADD_SOUND);
addItem(data, context.getString(R.string.AttachmentTypeSelectorAdapter_audio),
R.drawable.ic_attach_audio_holo_light, ADD_SOUND);
// addItem(data, "Record audio", R.drawable.ic_launcher_record_audio, RECORD_SOUND);
return data;
return data;
}
private static void addItem(List<IconListItem> list, String text, int resource, int id) {
list.add(new IconListItem(text, resource, id));
}
public static class IconListItem {
private final String mTitle;
private final int mResource;
private final int id;
public IconListItem(String title, int resource, int id) {
mResource = resource;
mTitle = title;
this.id = id;
}
public static class IconListItem {
private final String mTitle;
private final int mResource;
private final int id;
public int getCommand() {
return id;
}
public String getTitle() {
return mTitle;
}
public IconListItem(String title, int resource, int id) {
mResource = resource;
mTitle = title;
this.id = id;
}
public int getResource() {
return mResource;
}
}
public int getCommand() {
return id;
}
public String getTitle() {
return mTitle;
}
public int getResource() {
return mResource;
}
}
}

View File

@@ -22,6 +22,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.MasterSecret;

View File

@@ -1,6 +1,6 @@
/**
/**
* 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
@@ -10,15 +10,16 @@
* 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.mms;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.InetAddress;
import android.content.Context;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.Uri;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
@@ -42,57 +43,58 @@ import org.thoughtcrime.securesms.util.Conversions;
import ws.com.google.android.mms.MmsException;
import android.content.Context;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.Uri;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.InetAddress;
public class MmsCommunication {
protected static MmsConnectionParameters getMmsConnectionParameters(Context context) throws MmsException {
Cursor cursor = DatabaseFactory.getMmsDatabase(context).getCarrierMmsInformation();
protected static MmsConnectionParameters getMmsConnectionParameters(Context context, String apn)
throws MmsException
{
Cursor cursor = DatabaseFactory.getMmsDatabase(context).getCarrierMmsInformation(apn);
try {
if (cursor == null || !cursor.moveToFirst())
throw new MmsException("No carrier MMS information available.");
throw new MmsException("No carrier MMS information available.");
do {
String mmsc = cursor.getString(cursor.getColumnIndexOrThrow("mmsc"));
String proxy = cursor.getString(cursor.getColumnIndexOrThrow("mmsproxy"));
String port = cursor.getString(cursor.getColumnIndexOrThrow("mmsport"));
if (mmsc != null && !mmsc.equals(""))
return new MmsConnectionParameters(mmsc, proxy, port);
String mmsc = cursor.getString(cursor.getColumnIndexOrThrow("mmsc"));
String proxy = cursor.getString(cursor.getColumnIndexOrThrow("mmsproxy"));
String port = cursor.getString(cursor.getColumnIndexOrThrow("mmsport"));
if (mmsc != null && !mmsc.equals(""))
return new MmsConnectionParameters(mmsc, proxy, port);
} while (cursor.moveToNext());
throw new MmsException("No carrier MMS information available.");
} finally {
if (cursor != null)
cursor.close();
cursor.close();
}
}
protected static void checkRouteToHost(Context context, MmsConnectionParameters parameters, String url) throws IOException {
if (parameters == null || !parameters.hasProxy())
checkRouteToHost(context, Uri.parse(url).getHost());
else
checkRouteToHost(context, parameters.getProxy());
}
private static void checkRouteToHost(Context context, String host) throws IOException {
InetAddress inetAddress = InetAddress.getByName(host);
byte[] ipAddressBytes = inetAddress.getAddress();
int ipAddress = Conversions.byteArrayToIntLittleEndian(ipAddressBytes, 0);
ConnectivityManager manager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (!manager.requestRouteToHost(MmsDownloader.TYPE_MOBILE_MMS, ipAddress))
throw new IOException("Connection manager could not obtain route to host.");
throw new IOException("Connection manager could not obtain route to host.");
// if (!manager.requestRouteToHost(ConnectivityManager.TYPE_MOBILE, ipAddress))
// throw new IOException("Connection manager could not obtain route to host.");
// throw new IOException("Connection manager could not obtain route to host.");
}
protected static HttpClient constructHttpClient(MmsConnectionParameters mmsConfig) {
HttpParams params = new BasicHttpParams();
HttpConnectionParams.setStaleCheckingEnabled(params, false);
@@ -102,11 +104,11 @@ public class MmsCommunication {
HttpClientParams.setRedirecting(params, false);
HttpProtocolParams.setUserAgent(params, "TextSecure/0.1");
HttpProtocolParams.setContentCharset(params, "UTF-8");
if (mmsConfig.hasProxy()) {
ConnRouteParams.setDefaultProxy(params, new HttpHost(mmsConfig.getProxy(), mmsConfig.getPort()));
}
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
schemeRegistry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
@@ -114,47 +116,47 @@ public class MmsCommunication {
ClientConnectionManager manager = new ThreadSafeClientConnManager(params, schemeRegistry);
return new DefaultHttpClient(manager, params);
}
protected static byte[] parseResponse(HttpEntity entity) throws IOException {
if (entity == null || entity.getContentLength() == 0)
throw new IOException("Null response");
byte[] responseBytes = new byte[(int)entity.getContentLength()];
DataInputStream dataInputStream = new DataInputStream(entity.getContent());
dataInputStream.readFully(responseBytes);
dataInputStream.close();
entity.consumeContent();
return responseBytes;
return responseBytes;
}
protected static class MmsConnectionParameters {
private final String mmsc;
private final String proxy;
private final String port;
public MmsConnectionParameters(String mmsc, String proxy, String port) {
this.mmsc = mmsc;
this.proxy = proxy;
this.port = port;
}
public boolean hasProxy() {
return proxy != null && proxy.trim().length() != 0;
}
public String getMmsc() {
return mmsc;
}
public String getProxy() {
return proxy;
}
public int getPort() {
if (port == null || port.trim().length() == 0)
return 80;
return 80;
return Integer.parseInt(port);
}
}

View File

@@ -1,6 +1,6 @@
/**
/**
* 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
@@ -10,18 +10,15 @@
* 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.mms;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import android.content.Context;
import android.util.Log;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
@@ -30,38 +27,32 @@ import org.apache.http.client.methods.HttpGet;
import ws.com.google.android.mms.MmsException;
import android.content.Context;
import android.util.Log;
import java.io.IOException;
public class MmsDownloadHelper extends MmsCommunication {
private static byte[] makeRequest(MmsConnectionParameters connectionParameters, String url) throws ClientProtocolException, IOException {
try {
HttpClient client = constructHttpClient(connectionParameters);
URI hostUrl = new URI(url);
HttpHost target = new HttpHost(hostUrl.getHost(), hostUrl.getPort(), HttpHost.DEFAULT_SCHEME_NAME);
HttpRequest request = new HttpGet(url);
request.setParams(client.getParams());
request.addHeader("Accept", "*/*, application/vnd.wap.mms-message, application/vnd.wap.sic");
HttpResponse response = client.execute(target, request);
StatusLine status = response.getStatusLine();
if (status.getStatusCode() != 200)
throw new IOException("Non-successful HTTP response: " + status.getReasonPhrase());
return parseResponse(response.getEntity());
} catch (URISyntaxException use) {
Log.w("MmsDownlader", use);
throw new IOException("Bad URI syntax");
}
private static byte[] makeRequest(MmsConnectionParameters connectionParameters, String url)
throws ClientProtocolException, IOException
{
HttpClient client = constructHttpClient(connectionParameters);
HttpGet request = new HttpGet(url);
request.setParams(client.getParams());
request.addHeader("Accept", "*/*, application/vnd.wap.mms-message, application/vnd.wap.sic");
HttpResponse response = client.execute(request);
StatusLine status = response.getStatusLine();
if (status.getStatusCode() != 200)
throw new IOException("Non-successful HTTP response: " + status.getReasonPhrase());
return parseResponse(response.getEntity());
}
public static byte[] retrieveMms(Context context, String url) throws IOException {
public static byte[] retrieveMms(Context context, String url, String apn) throws IOException {
try {
MmsConnectionParameters connectionParameters = getMmsConnectionParameters(context);
MmsConnectionParameters connectionParameters = getMmsConnectionParameters(context, apn);
checkRouteToHost(context, connectionParameters, url);
return makeRequest(connectionParameters, url);
} catch (MmsException me) {

View File

@@ -1,79 +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.mms;
import android.content.Context;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessageRecord;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.MmsMessageRecord;
import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.MultimediaMessagePdu;
import ws.com.google.android.mms.pdu.NotificationInd;
import ws.com.google.android.mms.pdu.PduHeaders;
public class MmsFactory {
public static MmsMessageRecord getMms(Context context, MasterSecret masterSecret, MessageRecord record, long mmsType, long box) throws MmsException {
Log.w("MmsFactory", "MMS Type: " + mmsType);
if (mmsType == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND) {
return getNotificationMmsRecord(context, record);
} else {
return getMediaMmsRecord(context, masterSecret, record, box);
}
}
private static MmsMessageRecord getNotificationMmsRecord(Context context, MessageRecord record) throws MmsException {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
NotificationInd notification = database.getNotificationMessage(record.getId());
return new MmsMessageRecord(record, notification.getContentLocation(), notification.getMessageSize(),
notification.getExpiry(), notification.getStatus(), notification.getTransactionId());
}
private static MmsMessageRecord getMediaMmsRecord(Context context, MasterSecret masterSecret, MessageRecord record, long box) throws MmsException {
MmsDatabase database = DatabaseFactory.getEncryptingMmsDatabase(context, masterSecret);
MultimediaMessagePdu msg = database.getMediaMessage(record.getId());
SlideDeck slideDeck = new SlideDeck(context, masterSecret, msg.getBody());
return new MmsMessageRecord(context, record, slideDeck, box);
}
// private static void setBodyIfText(SlideModel slide, MessageRecord record) {
// if ((slide != null) && slide.hasText()) {
// TextModel tm = slide.getText();
//
// if (tm.isDrmProtected()) {
// record.setBody("DRM protected");
// } else {
// record.setBody(tm.getText());
// }
// }
// }
//
// private static SlideshowModel getSlideshowModel(Context context, long messageId) throws MmsException {
// LayoutManager.init(context);
// MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
// MultimediaMessagePdu msg = database.getMediaMessage(messageId);
// return SlideshowModel.createFromPduBody(context, msg.getBody());
// }
}

View File

@@ -1,6 +1,6 @@
/**
/**
* 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
@@ -10,63 +10,53 @@
* 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.mms;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import android.content.Context;
import android.util.Log;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.thoughtcrime.securesms.util.Hex;
import ws.com.google.android.mms.MmsException;
import android.content.Context;
import android.util.Log;
import java.io.IOException;
public class MmsSendHelper extends MmsCommunication {
private static byte[] makePost(MmsConnectionParameters parameters, byte[] mms) throws ClientProtocolException, IOException {
Log.w("MmsSender", "Sending MMS1 of length: " + mms.length);
try {
HttpClient client = constructHttpClient(parameters);
URI hostUrl = new URI(parameters.getMmsc());
HttpHost target = new HttpHost(hostUrl.getHost(), hostUrl.getPort(), HttpHost.DEFAULT_SCHEME_NAME);
HttpPost request = new HttpPost(parameters.getMmsc());
ByteArrayEntity entity = new ByteArrayEntity(mms);
entity.setContentType("application/vnd.wap.mms-message");
request.setEntity(entity);
request.setParams(client.getParams());
request.addHeader("Accept", "*/*, application/vnd.wap.mms-message, application/vnd.wap.sic");
HttpResponse response = client.execute(target, request);
StatusLine status = response.getStatusLine();
if (status.getStatusCode() != 200)
throw new IOException("Non-successful HTTP response: " + status.getReasonPhrase());
return parseResponse(response.getEntity());
} catch (URISyntaxException use) {
Log.w("MmsDownlader", use);
throw new IOException("Bad URI syntax");
}
HttpClient client = constructHttpClient(parameters);
HttpPost request = new HttpPost(parameters.getMmsc());
ByteArrayEntity entity = new ByteArrayEntity(mms);
entity.setContentType("application/vnd.wap.mms-message");
request.setEntity(entity);
request.setParams(client.getParams());
request.addHeader("Accept", "*/*, application/vnd.wap.mms-message, application/vnd.wap.sic");
HttpResponse response = client.execute(request);
StatusLine status = response.getStatusLine();
if (status.getStatusCode() != 200)
throw new IOException("Non-successful HTTP response: " + status.getReasonPhrase());
return parseResponse(response.getEntity());
}
public static byte[] sendMms(Context context, byte[] mms) throws IOException {
public static byte[] sendMms(Context context, byte[] mms, String apn) throws IOException {
Log.w("MmsSender", "Sending MMS of length: " + mms.length);
try {
MmsConnectionParameters parameters = getMmsConnectionParameters(context);
MmsConnectionParameters parameters = getMmsConnectionParameters(context, apn);
checkRouteToHost(context, parameters, parameters.getMmsc());
return makePost(parameters, mms);
} catch (MmsException me) {

View File

@@ -18,6 +18,7 @@ package org.thoughtcrime.securesms.mms;
import java.io.UnsupportedEncodingException;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
import org.thoughtcrime.securesms.crypto.MasterSecret;

View File

@@ -1,6 +1,6 @@
/**
/**
* 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
@@ -10,38 +10,38 @@
* 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.protocol;
import java.nio.ByteBuffer;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.InvalidKeyException;
import org.thoughtcrime.securesms.crypto.InvalidMessageException;
import org.thoughtcrime.securesms.crypto.PublicKey;
import org.thoughtcrime.securesms.util.Conversions;
import android.util.Log;
import java.nio.ByteBuffer;
/**
* Parses and serializes the encrypted message format.
*
*
* @author Moxie Marlinspike
*/
public class Message {
public static final int SUPPORTED_VERSION = 1;
private static final int VERSION_LENGTH = 1;
private static final int SENDER_KEY_ID_LENGTH = 3;
private static final int RECEIVER_KEY_ID_LENGTH = 3;
private static final int NEXT_KEY_LENGTH = PublicKey.KEY_SIZE;
private static final int COUNTER_LENGTH = 3;
public static final int HEADER_LENGTH = VERSION_LENGTH + SENDER_KEY_ID_LENGTH + RECEIVER_KEY_ID_LENGTH + COUNTER_LENGTH + NEXT_KEY_LENGTH;
private static final int VERSION_OFFSET = 0;
private static final int SENDER_KEY_ID_OFFSET = VERSION_OFFSET + VERSION_LENGTH;
private static final int RECEIVER_KEY_ID_OFFSET = SENDER_KEY_ID_OFFSET + SENDER_KEY_ID_LENGTH;
@@ -55,9 +55,9 @@ public class Message {
private int messageVersion;
private int supportedVersion;
private byte[] message;
private PublicKey nextKey;
public Message(int senderKeyId, int receiverKeyId, PublicKey nextKey, int counter, byte[] message, int messageVersion, int supportedVersion) {
this.senderKeyId = senderKeyId;
this.receiverKeyId = receiverKeyId;
@@ -67,33 +67,33 @@ public class Message {
this.messageVersion = messageVersion;
this.supportedVersion = supportedVersion;
}
public Message(byte[] messageBytes) throws InvalidMessageException {
try {
if (messageBytes.length <= HEADER_LENGTH)
throw new InvalidMessageException("Message is shorter than headers.");
this.messageVersion = Conversions.highBitsToInt(messageBytes[VERSION_OFFSET]);
this.supportedVersion = Conversions.lowBitsToInt(messageBytes[VERSION_OFFSET]);
Log.w("Message", "Message Version: " + messageVersion);
Log.w("Message", "Supported Version: " + supportedVersion);
if (messageVersion > SUPPORTED_VERSION)
throw new InvalidMessageException("Message protocol version not supported: " + messageVersion);
this.senderKeyId = Conversions.byteArrayToMedium(messageBytes, SENDER_KEY_ID_OFFSET);
this.receiverKeyId = Conversions.byteArrayToMedium(messageBytes, RECEIVER_KEY_ID_OFFSET);
this.counter = Conversions.byteArrayToMedium(messageBytes, COUNTER_OFFSET);
Log.w("Message", "Parsed current version: " + messageVersion + " supported version: " + supportedVersion);
byte[] nextKeyBytes = new byte[NEXT_KEY_LENGTH];
byte[] textBytes = new byte[messageBytes.length - HEADER_LENGTH];
System.arraycopy(messageBytes, NEXT_KEY_OFFSET, nextKeyBytes, 0, nextKeyBytes.length);
System.arraycopy(messageBytes, TEXT_OFFSET, textBytes, 0, textBytes.length);
Log.w("Message", "Pulling next key out of message...");
this.nextKey = new PublicKey(nextKeyBytes);
this.message = textBytes;
@@ -101,10 +101,10 @@ public class Message {
throw new AssertionError(ike);
}
}
public byte[] serialize() {
ByteBuffer buffer = ByteBuffer.allocate(HEADER_LENGTH + message.length);
Log.w("Message", "Constructing Message Version: (" + messageVersion + "," + supportedVersion + ")");
byte versionByte = Conversions.intsToByteHighAndLow(messageVersion, supportedVersion);
@@ -113,39 +113,39 @@ public class Message {
Log.w("Message", "Serializing next key into message...");
byte[] nextKeyBytes = nextKey.serialize();
byte[] counterBytes = Conversions.mediumToByteArray(counter);
buffer.put(versionByte);
buffer.put(senderKeyIdBytes);
buffer.put(receiverKeyIdBytes);
buffer.put(nextKeyBytes);
buffer.put(counterBytes);
buffer.put(message);
return buffer.array();
}
public int getHighestMutuallySupportedVersion() {
return Math.min(SUPPORTED_VERSION, this.supportedVersion);
}
public int getSenderKeyId() {
return this.senderKeyId;
}
public int getReceiverKeyId() {
return this.receiverKeyId;
}
public PublicKey getNextKey() {
return this.nextKey;
}
public int getCounter() {
return this.counter;
}
public byte[] getMessageText() {
return this.message;
}
}

View File

@@ -31,6 +31,6 @@ public class Prefix {
public static final String ASYMMETRIC_ENCRYPT = "?TextSecureAsymmetricEncrypt";
public static final String ASYMMETRIC_LOCAL_ENCRYPT = "?TextSecureAsymmetricLocalEncrypt";
public static final String PROCESSED_KEY_EXCHANGE = "?TextSecureKeyExchangd";
public static final String STALE_KEY_EXCHANGE = "?TextSecureKeyExchangs";
public static final String STALE_KEY_EXCHANGE = "?TextSecureKeyExchangs";
}

View File

@@ -19,7 +19,7 @@ public class Tag {
}
public static boolean isTagged(String message) {
return message.matches(".*[^\\s]" + WHITESPACE_TAG + "$");
return message != null && message.matches(".*[^\\s]" + WHITESPACE_TAG + "$");
}
public static String getTaggedMessage(String message) {

View File

@@ -1,6 +1,6 @@
/**
/**
* 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
@@ -10,22 +10,12 @@
* 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.providers;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.service.KeyCachingService;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentProvider;
@@ -41,6 +31,16 @@ import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.service.KeyCachingService;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class PartProvider extends ContentProvider {
private static final String CONTENT_URI_STRING = "content://org.thoughtcrime.provider.securesms/part";
@@ -48,7 +48,7 @@ public class PartProvider extends ContentProvider {
private static final int SINGLE_ROW = 1;
private static final UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI("org.thoughtcrime.provider.securesms", "part/#", SINGLE_ROW);
@@ -58,85 +58,84 @@ public class PartProvider extends ContentProvider {
private NewKeyReceiver receiver;
@Override
public boolean onCreate() {
public boolean onCreate() {
initializeMasterSecret();
return true;
}
public static boolean isAuthority(Uri uri) {
return uriMatcher.match(uri) != -1;
}
private File copyPartToTemporaryFile(MasterSecret masterSecret, long partId) throws IOException {
InputStream in = DatabaseFactory.getEncryptingPartDatabase(getContext(), masterSecret).getPartStream(partId);
File tmpDir = getContext().getDir("tmp", 0);
File tmpFile = File.createTempFile("test", ".jpg", tmpDir);
FileOutputStream fout = new FileOutputStream(tmpFile);
byte[] buffer = new byte[512];
int read;
while ((read = in.read(buffer)) != -1)
fout.write(buffer, 0, read);
in.close();
return tmpFile;
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
Log.w("PartProvider", "openFile() called!");
if (this.masterSecret == null)
return null;
switch (uriMatcher.match(uri)) {
case SINGLE_ROW:
Log.w("PartProvider", "Parting out a single row...");
try {
int partId = Integer.parseInt(uri.getPathSegments().get(1));
File tmpFile = copyPartToTemporaryFile(masterSecret, partId);
ParcelFileDescriptor pdf = ParcelFileDescriptor.open(tmpFile, ParcelFileDescriptor.MODE_READ_ONLY);
tmpFile.delete();
return pdf;
int partId = Integer.parseInt(uri.getPathSegments().get(1));
File tmpFile = copyPartToTemporaryFile(masterSecret, partId);
ParcelFileDescriptor pdf = ParcelFileDescriptor.open(tmpFile, ParcelFileDescriptor.MODE_READ_ONLY);
tmpFile.delete();
return pdf;
} catch (IOException ioe) {
Log.w("PartProvider", ioe);
throw new FileNotFoundException("Error opening file");
Log.w("PartProvider", ioe);
throw new FileNotFoundException("Error opening file");
}
}
throw new FileNotFoundException("Request for bad part.");
}
@Override
public int delete(Uri arg0, String arg1, String[] arg2) {
public int delete(Uri arg0, String arg1, String[] arg2) {
return 0;
}
@Override
public String getType(Uri arg0) {
public String getType(Uri arg0) {
return null;
}
@Override
public Uri insert(Uri arg0, ContentValues arg1) {
public Uri insert(Uri arg0, ContentValues arg1) {
return null;
}
@Override
public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3, String arg4) {
public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3, String arg4) {
return null;
}
@Override
public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
return 0;
}
private void initializeWithMasterSecret(MasterSecret masterSecret) {
Log.w("PartProvider", "Got master secret: " + masterSecret);
Log.w("PartProvider", "Got master secret: " + masterSecret);
this.masterSecret = masterSecret;
}
@@ -148,15 +147,15 @@ public class PartProvider extends ContentProvider {
Intent bindIntent = new Intent(getContext(), KeyCachingService.class);
getContext().bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection serviceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
KeyCachingService keyCachingService = ((KeyCachingService.KeyCachingBinder)service).getService();
MasterSecret masterSecret = keyCachingService.getMasterSecret();
KeyCachingService keyCachingService = ((KeyCachingService.KeyCachingBinder)service).getService();
MasterSecret masterSecret = keyCachingService.getMasterSecret();
initializeWithMasterSecret(masterSecret);
initializeWithMasterSecret(masterSecret);
PartProvider.this.getContext().unbindService(this);
PartProvider.this.getContext().unbindService(this);
}
public void onServiceDisconnected(ComponentName name) {}
@@ -164,7 +163,7 @@ public class PartProvider extends ContentProvider {
private class NewKeyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
public void onReceive(Context context, Intent intent) {
Log.w("SendReceiveService", "Got a MasterSecret broadcast...");
initializeWithMasterSecret((MasterSecret)intent.getParcelableExtra("master_secret"));
}

View File

@@ -50,9 +50,10 @@ public class Recipient implements Parcelable {
}
public Recipient(Parcel in) {
this.name = in.readString();
this.number = in.readString();
this.contactUri = in.readParcelable(null);
this.name = in.readString();
this.number = in.readString();
this.contactUri = in.readParcelable(null);
this.contactPhoto = in.readParcelable(null);
}
public Uri getContactUri() {
@@ -75,6 +76,7 @@ public class Recipient implements Parcelable {
dest.writeString(name);
dest.writeString(number);
dest.writeParcelable(contactUri, 0);
dest.writeParcelable(contactPhoto, 0);
}
public String toShortString() {

View File

@@ -19,7 +19,6 @@ package org.thoughtcrime.securesms.recipients;
import android.content.Context;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.telephony.PhoneNumberUtils;
import android.util.Log;
import org.thoughtcrime.securesms.R;
@@ -146,7 +145,7 @@ public class RecipientFactory {
int end = recipient.indexOf('>');
String value = recipient.substring(begin + 1, end);
if (PhoneNumberUtils.isWellFormedSmsAddress(value))
if (NumberUtil.isValidSmsOrEmail(value))
return value;
else
throw new RecipientFormattingException("Bracketed value: " + value + " is not valid.");

View File

@@ -1,6 +1,6 @@
/**
/**
* 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
@@ -10,7 +10,7 @@
* 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/>.
*/
@@ -20,15 +20,15 @@ public class RecipientFormattingException extends Exception {
public RecipientFormattingException() {
super();
}
public RecipientFormattingException(String message) {
super(message);
}
public RecipientFormattingException(String message, Throwable nested) {
super(message, nested);
}
public RecipientFormattingException(Throwable nested) {
super(nested);
}

Some files were not shown because too many files have changed in this diff Show More