summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDmitry Shkil <dshkil@cogniance.com>2015-12-08 16:39:25 +0200
committeremancebo <emancebo@cyngn.com>2015-12-08 09:23:22 -0800
commit939a4c8552b17e062a225485538b77e735641462 (patch)
tree067d8512ac899b9dbd772a03ade251e9e664a835
parent3d4deaf5320efb43600e2f5fff783f07b9f244b4 (diff)
downloadandroid_packages_apps_Messaging-staging/messaging-whisperpush.tar.gz
android_packages_apps_Messaging-staging/messaging-whisperpush.tar.bz2
android_packages_apps_Messaging-staging/messaging-whisperpush.zip
Integrate WhisperPush secure messaging into Messagingstaging/messaging-whisperpush
This will enable communication to Signal users, secure SMS, MMS and Group messaging! Change-Id: Ia8b744abab48b3ffb9e19466f77c36553301e42f
-rw-r--r--Android.mk4
-rw-r--r--AndroidManifest.xml127
-rw-r--r--res/layout/conversation_message_view.xml7
-rw-r--r--res/layout/dialog_mixed_participants_warning.xml25
-rw-r--r--res/values/colors.xml4
-rw-r--r--res/values/strings.xml38
-rw-r--r--res/values/styles.xml9
-rw-r--r--res/values/versions.xml2
-rw-r--r--res/values/whisperpush_colors.xml6
-rw-r--r--res/xml-v21/preferences_application.xml10
-rw-r--r--res/xml-v23/preferences_application.xml10
-rw-r--r--res/xml/preferences_application.xml10
-rw-r--r--src/com/android/messaging/BugleApplication.java6
-rw-r--r--src/com/android/messaging/WhisperPushMessagingBridge.java304
-rw-r--r--src/com/android/messaging/datamodel/BugleDatabaseOperations.java39
-rw-r--r--src/com/android/messaging/datamodel/DatabaseHelper.java4
-rw-r--r--src/com/android/messaging/datamodel/DatabaseUpgradeHelper.java10
-rw-r--r--src/com/android/messaging/datamodel/action/InsertNewMessageAction.java12
-rw-r--r--src/com/android/messaging/datamodel/action/ProcessSentMessageAction.java18
-rw-r--r--src/com/android/messaging/datamodel/action/SendMessageAction.java73
-rw-r--r--src/com/android/messaging/datamodel/data/ConversationMessageData.java13
-rw-r--r--src/com/android/messaging/datamodel/data/ConversationParticipantsData.java2
-rw-r--r--src/com/android/messaging/datamodel/data/DraftMessageData.java29
-rw-r--r--src/com/android/messaging/datamodel/data/MessageData.java41
-rw-r--r--src/com/android/messaging/datamodel/data/ParticipantData.java4
-rw-r--r--src/com/android/messaging/metrics/Event.java25
-rw-r--r--src/com/android/messaging/metrics/EventStatistic.java62
-rw-r--r--src/com/android/messaging/metrics/MetricsDatabase.java103
-rw-r--r--src/com/android/messaging/metrics/MetricsDatabaseHelper.java23
-rw-r--r--src/com/android/messaging/metrics/MetricsSendService.java55
-rw-r--r--src/com/android/messaging/metrics/MetricsTracker.java111
-rwxr-xr-xsrc/com/android/messaging/sms/MmsConfig.java5
-rw-r--r--src/com/android/messaging/sms/MmsUtils.java92
-rw-r--r--src/com/android/messaging/ui/ConversationDrawables.java6
-rw-r--r--src/com/android/messaging/ui/conversation/ComposeMessageView.java45
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationFragment.java30
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationMessageView.java25
-rw-r--r--src/com/android/messaging/ui/conversation/MixedParticipantsSecurityWarning.java76
-rw-r--r--src/com/android/messaging/util/SecureMessagingHelper.java103
-rw-r--r--tests/src/com/android/messaging/ui/conversation/ComposeMessageViewTest.java2
40 files changed, 1515 insertions, 55 deletions
diff --git a/Android.mk b/Android.mk
index 725c89d..0f0bf3b 100644
--- a/Android.mk
+++ b/Android.mk
@@ -31,6 +31,7 @@ LOCAL_RESOURCE_DIR += frameworks/opt/chips/res
LOCAL_RESOURCE_DIR += frameworks/opt/colorpicker/res
LOCAL_RESOURCE_DIR += frameworks/opt/photoviewer/res
LOCAL_RESOURCE_DIR += frameworks/opt/photoviewer/activity/res
+LOCAL_RESOURCE_DIR += external/whispersystems/libwhisperpush/res
LOCAL_STATIC_JAVA_LIBRARIES := android-common
LOCAL_STATIC_JAVA_LIBRARIES += android-common-framesequence
@@ -45,6 +46,7 @@ LOCAL_STATIC_JAVA_LIBRARIES += libchips
LOCAL_STATIC_JAVA_LIBRARIES += libphotoviewer
LOCAL_STATIC_JAVA_LIBRARIES += libphonenumber
LOCAL_STATIC_JAVA_LIBRARIES += colorpicker
+LOCAL_STATIC_JAVA_LIBRARIES += libwhisperpush
include $(LOCAL_PATH)/version.mk
@@ -57,6 +59,7 @@ LOCAL_AAPT_FLAGS += --extra-packages com.android.ex.chips
LOCAL_AAPT_FLAGS += --extra-packages com.android.vcard
LOCAL_AAPT_FLAGS += --extra-packages com.android.ex.photo
LOCAL_AAPT_FLAGS += --extra-packages com.android.colorpicker
+LOCAL_AAPT_FLAGS += --extra-packages org.whispersystems.whisperpush
ifdef TARGET_BUILD_APPS
LOCAL_JNI_SHARED_LIBRARIES := libframesequence libgiftranscode
@@ -74,6 +77,7 @@ ifeq (eng,$(TARGET_BUILD_VARIANT))
else
LOCAL_PROGUARD_FLAG_FILES += proguard-release.flags
endif
+LOCAL_PROGUARD_FLAG_FILES += ../../../external/whispersystems/libwhisperpush/proguard.flags
LOCAL_JACK_ENABLED := disabled
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 8fe8fae..56472a8 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -16,9 +16,10 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.messaging"
+ xmlns:tools="http://schemas.android.com/tools"
android:installLocation="internalOnly">
- <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="23" />
+ <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="23" />
<!-- Application holds CPU wakelock while working in background -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
@@ -55,6 +56,26 @@
<uses-feature android:name="android.hardware.microphone" android:required="false" />
<uses-feature android:name="android.hardware.screen.portrait" android:required="false" />
+ <!-- Added for WhisperPush -->
+ <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
+ <uses-permission android:name="android.permission.INTERCEPT_SMS"/>
+ <uses-permission android:name="android.permission.MODIFY_PROTECTED_SMS_LIST"/>
+ <uses-permission android:name="android.permission.RECEIVE_PROTECTED_SMS"/>
+ <uses-permission android:name="android.permission.BROADCAST_SMS"
+ tools:ignore="ProtectedPermissions"/>
+ <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
+ <uses-permission android:name="com.cyngn.stats.SEND_ANALYTICS"/>
+
+ <permission
+ android:name="com.android.messaging.permission.C2D_MESSAGE"
+ android:protectionLevel="signature"/>
+ <uses-permission android:name="com.android.messaging.permission.C2D_MESSAGE"/>
+
+ <permission
+ android:name="com.android.messaging.whisperpush.permissions.REGISTER"
+ android:protectionLevel="signatureOrSystem"/>
+
<application
android:name="com.android.messaging.BugleApplication"
android:allowBackup="false"
@@ -519,6 +540,110 @@
</activity>
<service android:name="android.support.v7.mms.MmsService"/>
+
+ <service android:exported="false"
+ android:name=".metrics.MetricsSendService"/>
+
+ <!-- WhisperPush -->
+
+ <meta-data android:name="com.google.android.gms.version"
+ android:value="@integer/google_play_services_version" />
+
+ <activity android:name="org.whispersystems.whisperpush.ui.RegistrationActivity"
+ android:windowSoftInputMode="stateUnchanged"
+ android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
+ android:theme="@style/Theme.WhisperPush.Main">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ </intent-filter>
+ </activity>
+
+ <activity android:name="org.whispersystems.whisperpush.ui.GooglePlayServicesUpdateActivity"
+ android:theme="@style/Theme.WhisperPush.Main"/>
+
+ <activity android:name="org.whispersystems.whisperpush.ui.CountrySelectionActivity"
+ android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
+ android:theme="@style/Theme.WhisperPush.Main"/>
+
+ <activity android:name="org.whispersystems.whisperpush.ui.ErrorAndResetActivity"
+ android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
+ android:theme="@style/Theme.WhisperPush.Main"/>
+
+ <activity android:name="org.whispersystems.whisperpush.ui.RegistrationProgressActivity"
+ android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
+ android:theme="@style/Theme.WhisperPush.Main"/>
+
+ <activity android:name="org.whispersystems.whisperpush.ui.RegistrationProblemsActivity"
+ android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
+ android:theme="@style/Theme.WhisperPush.Dialog"/>
+
+ <activity android:name="org.whispersystems.whisperpush.ui.RegistrationCompletedActivity"
+ android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
+ android:theme="@style/Theme.WhisperPush.Main"/>
+
+ <activity android:name="org.whispersystems.whisperpush.ui.PreferenceActivity"
+ android:label="@string/pref_whisperpush_title"
+ android:theme="@style/Theme.WhisperPush.Main"/>
+
+ <activity android:name="org.whispersystems.whisperpush.ui.ReviewIdentitiesActivity"
+ android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
+ android:label="@string/pref_review_keys__title"
+ android:theme="@style/Theme.WhisperPush.Main"/>
+
+ <activity android:name="org.whispersystems.whisperpush.ui.ViewIdentityActivity"
+ android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
+ android:theme="@style/Theme.WhisperPush.Main"/>
+
+ <activity android:name="org.whispersystems.whisperpush.ui.ViewMyIdentityActivity"
+ android:label="@string/pref_my_identity__title"
+ android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
+ android:theme="@style/Theme.WhisperPush.Main"/>
+
+ <activity android:name="org.whispersystems.whisperpush.ui.ViewNewIdentityActivity"
+ android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
+ android:theme="@style/Theme.WhisperPush.Main"/>
+
+ <activity android:name="org.whispersystems.whisperpush.ui.VerifyIdentityActivity"
+ android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
+ android:theme="@style/Theme.WhisperPush.Main"/>
+
+ <activity android:name="org.whispersystems.whisperpush.ui.VerifyIdentitiesActivity"
+ android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
+ android:theme="@style/Theme.WhisperPush.Main"/>
+
+ <service android:enabled="true"
+ android:name="org.whispersystems.whisperpush.service.RegistrationService"
+ android:permission="com.android.mms.whisperpush.permissions.REGISTER"/>
+
+ <service android:enabled="true" android:name="org.whispersystems.whisperpush.service.SendReceiveService"/>
+ <service android:enabled="true" android:name="org.whispersystems.whisperpush.service.DirectoryRefreshService"/>
+ <!--service android:name="com.android.mms.receive.HandleSecureMmsService" android:exported="false" />
+ <service android:exported="false"
+ android:name=".metrics.MetricsSendService"/-->
+
+ <receiver android:name="org.whispersystems.whisperpush.sms.IncomingSmsListener"
+ android:enabled="false"
+ android:exported="true">
+ <intent-filter android:priority="2147483647">
+ <action android:name="android.provider.Telephony.ACTION_PROTECTED_SMS_RECEIVED"/>
+ <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
+ </intent-filter>
+ </receiver>
+
+ <receiver android:name="org.whispersystems.whisperpush.gcm.GcmReceiver"
+ android:permission="com.google.android.c2dm.permission.SEND" >
+ <intent-filter>
+ <action android:name="com.google.android.c2dm.intent.RECEIVE" />
+ <category android:name="com.android.messaging" />
+ </intent-filter>
+ </receiver>
+
+ <receiver android:name="org.whispersystems.whisperpush.service.DirectoryRefreshListener">
+ <intent-filter>
+ <action android:name="org.whispersystems.whisperpush.DIRECTORY_REFRESH"/>
+ <action android:name="android.intent.action.BOOT_COMPLETED" />
+ </intent-filter>
+ </receiver>
</application>
</manifest>
diff --git a/res/layout/conversation_message_view.xml b/res/layout/conversation_message_view.xml
index daad600..155e356 100644
--- a/res/layout/conversation_message_view.xml
+++ b/res/layout/conversation_message_view.xml
@@ -135,6 +135,13 @@
android:importantForAccessibility="noHideDescendants" >
<TextView
+ android:id="@+id/message_secured"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/ConversationMessageStatus"
+ android:visibility="gone" />
+
+ <TextView
android:id="@+id/message_sender_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/res/layout/dialog_mixed_participants_warning.xml b/res/layout/dialog_mixed_participants_warning.xml
new file mode 100644
index 0000000..c4d49e6
--- /dev/null
+++ b/res/layout/dialog_mixed_participants_warning.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingLeft="24dp"
+ android:paddingRight="24dp">
+
+ <TextView
+ style="@style/DialogTextAppearanceGrey"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="16dp"
+ android:layout_marginTop="16dp"
+ android:text="@string/mixed_participants_warning_text" />
+
+ <CheckBox
+ android:id="@+id/dont_show_again"
+ style="@style/CheckBoxStyleGrey"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="-7dp"
+ android:text="@string/mixed_participants_warning_checkbox_text" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
index f33e105..610eab1 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -68,6 +68,7 @@
<color name="message_error_bubble_color_incoming">#e2e2e2</color>
<color name="message_audio_button_color_incoming">#ffffffff</color>
<color name="message_bubble_color_selected">#8BC34A</color>
+ <color name="message_bubble_color_secured">#448aff</color>
<color name="message_image_selected_tint">#80689F38</color>
<color name="generic_video_icon">#ff808080</color>
@@ -171,4 +172,7 @@
<color name="fastscroll_preview_text_color">#ffffff</color>
<color name="google_gray">#F1F1F1</color>
+
+ <color name="dialog_text_grey">#858585</color>
+ <color name="dialog_btn_grey">#c1c1c6</color>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index ec675ef..5bdc769 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -140,7 +140,8 @@
<string name="auto_delete_oldest_messages_confirmation">Delete messages older than <xliff:g id="duration">%s</xliff:g> and turn on auto-delete?</string>
<!-- ####### Strings to support the mms/sms service ####### -->
-
+ <!-- Prefix for accessibility to indicate if a message was sent securely -->
+ <string name="message_securely_sent">Securely Sent:\u0020</string>
<!-- Prefix for accessibility to indicate who the sender of a plain text message is. -->
<string name="incoming_text_sender_content_description"><xliff:g id="sender">%s</xliff:g> said</string>
<string name="outgoing_text_sender_content_description">You said</string>
@@ -549,34 +550,36 @@
<string name="participant_list_title">People in this conversation</string>
<!-- Title for the phone call button in the action bar that lets the user call a participant in an SMS conversation -->
<string name="action_call">Make a call</string>
+ <!-- Text to insert into message hint text for messages that will be sent securely -->
+ <string name="secure_text">\u0020secure\u0020</string>
<!-- The hint text shown in the message compose input box prompting the user to send a message -->
- <string name="compose_message_view_hint_text">Send message</string>
+ <string name="compose_message_view_hint_text">Send<xliff:g id="secure_text">%1$s</xliff:g>message</string>
<!-- The hint text shown in the message compose input box prompting the user to send a message when there are multiple SIMs. -->
- <string name="compose_message_view_hint_text_multi_sim">Send message&lt;br/&gt;&lt;small&gt;from <xliff:g id="sim_name">%s</xliff:g>&lt;/small&gt;</string>
+ <string name="compose_message_view_hint_text_multi_sim">Send<xliff:g id="secure_text">%1$s</xliff:g>message&lt;br/&gt;&lt;small&gt;from <xliff:g id="sim_name">%2$s</xliff:g>&lt;/small&gt;</string>
<!-- The hint text shown in the message compose input box when photo(s) is attached -->
<plurals name="compose_message_view_hint_text_photo">
- <item quantity="one">Send photo</item>
- <item quantity="other">Send photos</item>
+ <item quantity="one">Send<xliff:g id="secure_text">%1$s</xliff:g>photo</item>
+ <item quantity="other">Send<xliff:g id="secure_text">%1$s</xliff:g>photos</item>
</plurals>
<!-- The hint text shown in the message compose input box when audio recording(s) is attached -->
<plurals name="compose_message_view_hint_text_audio">
- <item quantity="one">Send audio</item>
- <item quantity="other">Send audios</item>
+ <item quantity="one">Send<xliff:g id="secure_text">%1$s</xliff:g>audio</item>
+ <item quantity="other">Send<xliff:g id="secure_text">%1$s</xliff:g>audios</item>
</plurals>
<!-- The hint text shown in the message compose input box when video(s) is attached -->
<plurals name="compose_message_view_hint_text_video">
- <item quantity="one">Send video</item>
- <item quantity="other">Send videos</item>
+ <item quantity="one">Send<xliff:g id="secure_text">%1$s</xliff:g>video</item>
+ <item quantity="other">Send<xliff:g id="secure_text">%1$s</xliff:g>videos</item>
</plurals>
<!-- The hint text shown in the message compose input box when vcard(s) is attached -->
<plurals name="compose_message_view_hint_text_vcard">
- <item quantity="one">Send contact card</item>
- <item quantity="other">Send contact cards</item>
+ <item quantity="one">Send<xliff:g id="secure_text">%1$s</xliff:g>contact card</item>
+ <item quantity="other">Send<xliff:g id="secure_text">%1$s</xliff:g>contact cards</item>
</plurals>
<!-- The hint text shown in the message compose input box when a single unknown attachment, or multiple different attachments are attached -->
<plurals name="compose_message_view_hint_text_attachments">
- <item quantity="one">Send attachment</item>
- <item quantity="other">Send attachments</item>
+ <item quantity="one">Send<xliff:g id="secure_text">%1$s</xliff:g>attachment</item>
+ <item quantity="other">Send<xliff:g id="secure_text">%1$s</xliff:g>attachments</item>
</plurals>
<!-- Announcement after the user adds or removes an attachment -->
<plurals name="attachment_changed_accessibility_announcement">
@@ -605,6 +608,15 @@
<string name="add_contact_confirmation_dialog_title">Add to Contacts?</string>
<!-- Alert dialog accept adding the given phone number to your contacts button. -->
<string name="add_contact_confirmation">Add Contact</string>
+ <!-- Alert dialog warning title regarding mixed security state of conversation participants -->
+ <string name="mixed_participants_warning_dialog_title">Not all contacts are secure</string>
+ <!-- Alert dialog warning content regarding mixed security state of conversation participants -->
+ <string name="mixed_participants_warning_text">To create secure group messages, all contacts must have secure phone numbers.</string>
+ <string name="mixed_participants_warning_checkbox_text">Don\'t show me this again</string>
+ <!-- Alert dialog warning actions regarding mixed security state of conversation participants -->
+ <string name="mixed_participants_send_as_sms_action">SEND AS SMS</string>
+ <string name="mixed_participants_cancel_action">CANCEL</string>
+
<!-- The text shown as a label before the message subject input box -->
<string name="compose_message_view_subject_hint_text">Subject</string>
<!-- The text shown as a label before the message subject input box -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index d45f2e1..84a3199 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -623,4 +623,13 @@
<item name="android:textColor">@android:color/white</item>
<item name="android:gravity">center</item>
</style>
+
+ <style name="DialogTextAppearanceGrey" parent="@android:style/TextAppearance.Theme.Dialog">
+ <item name="android:textColor">@color/dialog_text_grey</item>
+ </style>
+
+ <style name="CheckBoxStyleGrey" parent="@android:style/Widget.Holo.CompoundButton.CheckBox">
+ <item name="android:textColor">@color/dialog_btn_grey</item>
+ <item name="buttonTint">@color/dialog_btn_grey</item>
+ </style>
</resources>
diff --git a/res/values/versions.xml b/res/values/versions.xml
index e82b79e..14c6b47 100644
--- a/res/values/versions.xml
+++ b/res/values/versions.xml
@@ -16,7 +16,7 @@
-->
<resources>
<!-- DB version -->
- <string name="database_version" translatable="false">1</string>
+ <string name="database_version" translatable="false">2</string>
<!-- Version for shared preferences. This is used for handling prefs migration when old pref
keys are moved or renamed. You don't need to bump up the version number if you are just
diff --git a/res/values/whisperpush_colors.xml b/res/values/whisperpush_colors.xml
new file mode 100644
index 0000000..76efb18
--- /dev/null
+++ b/res/values/whisperpush_colors.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <color name="whisperpush_theme_primary">@color/action_bar_background_color</color>
+ <color name="whisperpush_theme_primary_dark">@color/action_bar_background_color_dark</color>
+ <color name="whisperpush_theme_accent">@color/action_bar_background_color</color>
+</resources> \ No newline at end of file
diff --git a/res/xml-v21/preferences_application.xml b/res/xml-v21/preferences_application.xml
index 5d8ee4c..1afe402 100644
--- a/res/xml-v21/preferences_application.xml
+++ b/res/xml-v21/preferences_application.xml
@@ -88,4 +88,14 @@
</PreferenceCategory>
+ <PreferenceScreen
+ android:key="pref_whisperpush"
+ android:persistent="false"
+ android:title="@string/pref_whisperpush_title"
+ android:summary="@string/pref_whisperpush_summary">
+ <intent
+ android:targetClass="org.whispersystems.whisperpush.ui.PreferenceActivity"
+ android:targetPackage="com.android.messaging" />
+ </PreferenceScreen>
+
</PreferenceScreen>
diff --git a/res/xml-v23/preferences_application.xml b/res/xml-v23/preferences_application.xml
index 8fbadc4..b8f72ac 100644
--- a/res/xml-v23/preferences_application.xml
+++ b/res/xml-v23/preferences_application.xml
@@ -89,4 +89,14 @@
</PreferenceCategory>
+ <PreferenceScreen
+ android:key="pref_whisperpush"
+ android:persistent="false"
+ android:title="@string/pref_whisperpush_title"
+ android:summary="@string/pref_whisperpush_summary">
+ <intent
+ android:targetClass="org.whispersystems.whisperpush.ui.PreferenceActivity"
+ android:targetPackage="com.android.messaging" />
+ </PreferenceScreen>
+
</PreferenceScreen>
diff --git a/res/xml/preferences_application.xml b/res/xml/preferences_application.xml
index 7a18d09..a2c2c5c 100644
--- a/res/xml/preferences_application.xml
+++ b/res/xml/preferences_application.xml
@@ -88,4 +88,14 @@
</PreferenceCategory>
+ <PreferenceScreen
+ android:key="pref_whisperpush"
+ android:persistent="false"
+ android:title="@string/pref_whisperpush_title"
+ android:summary="@string/pref_whisperpush_summary">
+ <intent
+ android:targetClass="org.whispersystems.whisperpush.ui.PreferenceActivity"
+ android:targetPackage="com.android.messaging" />
+ </PreferenceScreen>
+
</PreferenceScreen>
diff --git a/src/com/android/messaging/BugleApplication.java b/src/com/android/messaging/BugleApplication.java
index a5aea9f..1f0a44b 100644
--- a/src/com/android/messaging/BugleApplication.java
+++ b/src/com/android/messaging/BugleApplication.java
@@ -29,6 +29,7 @@ import android.support.v7.mms.MmsManager;
import android.telephony.CarrierConfigManager;
import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.metrics.MetricsSendService;
import com.android.messaging.receiver.SmsReceiver;
import com.android.messaging.sms.ApnDatabase;
import com.android.messaging.sms.BugleApnSettingsLoader;
@@ -46,6 +47,8 @@ import com.android.messaging.util.PhoneUtils;
import com.android.messaging.util.Trace;
import com.google.common.annotations.VisibleForTesting;
+import org.whispersystems.whisperpush.WhisperPush;
+
import java.io.File;
import java.lang.Thread.UncaughtExceptionHandler;
@@ -86,6 +89,8 @@ public class BugleApplication extends Application implements UncaughtExceptionHa
sSystemUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
Trace.endSection();
+
+ MetricsSendService.schedule(this);
}
@Override
@@ -121,6 +126,7 @@ public class BugleApplication extends Application implements UncaughtExceptionHa
if (OsUtil.isAtLeastM()) {
registerCarrierConfigChangeReceiver(context);
}
+ WhisperPush.setMessagingBridge(new WhisperPushMessagingBridge(this));
Trace.endSection();
}
diff --git a/src/com/android/messaging/WhisperPushMessagingBridge.java b/src/com/android/messaging/WhisperPushMessagingBridge.java
new file mode 100644
index 0000000..6344671
--- /dev/null
+++ b/src/com/android/messaging/WhisperPushMessagingBridge.java
@@ -0,0 +1,304 @@
+package com.android.messaging;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.provider.Telephony;
+import android.provider.Telephony.Sms;
+import android.support.v7.mms.MmsManager;
+import android.support.v7.mms.pdu.ContentType;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.messaging.datamodel.BugleDatabaseOperations;
+import com.android.messaging.datamodel.BugleNotifications;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.DatabaseWrapper;
+import com.android.messaging.datamodel.MessagingContentProvider;
+import com.android.messaging.datamodel.SyncManager;
+import com.android.messaging.datamodel.action.BugleActionToasts;
+import com.android.messaging.datamodel.data.MessageData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.mmslib.InvalidHeaderValueException;
+import com.android.messaging.mmslib.MmsException;
+import com.android.messaging.mmslib.SqliteWrapper;
+import com.android.messaging.mmslib.pdu.CharacterSets;
+import com.android.messaging.mmslib.pdu.EncodedStringValue;
+import com.android.messaging.mmslib.pdu.PduBody;
+import com.android.messaging.mmslib.pdu.PduPart;
+import com.android.messaging.mmslib.pdu.PduPersister;
+import com.android.messaging.mmslib.pdu.RetrieveConf;
+import com.android.messaging.sms.DatabaseMessages;
+import com.android.messaging.sms.MmsSmsUtils;
+import com.android.messaging.sms.MmsUtils;
+import com.android.messaging.util.LogUtil;
+import com.android.messaging.util.OsUtil;
+
+import org.whispersystems.whisperpush.api.MessagingBridge;
+import org.whispersystems.whisperpush.util.Util;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class WhisperPushMessagingBridge implements MessagingBridge {
+
+ private static final String TAG = "WPMessagingBridge";
+
+ private final Context mContext;
+
+ private static final int SUBSCRIPTION_ID = 0;
+
+ public WhisperPushMessagingBridge(Context context) {
+ mContext = context.getApplicationContext();
+ }
+
+ @Override
+ public void storeIncomingTextMessage(String sender, String message, long sentAt,
+ boolean read) {
+
+ // See com.android.messaging.datamodel.action.ReceiveSmsMessageAction for details
+
+ long receivedAt = System.currentTimeMillis();
+ ParticipantData self = ParticipantData.getSelfParticipant(SUBSCRIPTION_ID);
+ ParticipantData rawSender = ParticipantData.getFromRawPhoneBySimLocale(sender, SUBSCRIPTION_ID);
+ DataModel dataModel = DataModel.get();
+ DatabaseWrapper db = dataModel.getDatabase();
+ long threadId = MmsSmsUtils.Threads.getOrCreateThreadId(mContext, sender);
+ boolean blocked = BugleDatabaseOperations.isBlockedDestination(db,
+ rawSender.getNormalizedDestination());
+ String conversationId = BugleDatabaseOperations.getOrCreateConversationFromRecipient(db,
+ threadId, blocked, rawSender);
+ boolean messageInFocusedConversation =
+ dataModel.isFocusedConversation(conversationId);
+ boolean messageInObservableConversation =
+ dataModel.isNewMessageObservable(conversationId);
+ read = read || messageInFocusedConversation;
+ boolean seen = read || messageInObservableConversation || blocked;
+
+ final SyncManager syncManager = DataModel.get().getSyncManager();
+ syncManager.onNewMessageInserted(receivedAt);
+
+ Uri messageUri = null;
+ if (!OsUtil.isSecondaryUser()) { // see ReceiveSmsMessageAction for details
+ ContentValues messageValues = new ContentValues();
+ messageValues.put(Sms.THREAD_ID, threadId);
+ messageValues.put(Sms.ADDRESS, sender);
+ messageValues.put(Sms.BODY, message);
+ messageValues.put(Sms.DATE, receivedAt);
+ messageValues.put(Sms.DATE_SENT, sentAt);
+ messageValues.put(Sms.SUBSCRIPTION_ID, SUBSCRIPTION_ID);
+ messageValues.put(Sms.Inbox.READ, read ? 1 : 0);
+ // incoming messages are marked as seen in the telephony db
+ messageValues.put(Sms.Inbox.SEEN, 1);
+
+ // Insert into telephony
+ messageUri = mContext.getContentResolver().insert(Sms.Inbox.CONTENT_URI, messageValues);
+
+ if (messageUri != null) {
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "ReceiveSmsMessageAction: Inserted SMS message into telephony, "
+ + "uri = " + messageUri);
+ }
+ } else {
+ LogUtil.e(TAG, "ReceiveSmsMessageAction: Failed to insert SMS into telephony!");
+ }
+ }
+
+ db.beginTransaction();
+ try {
+ String participantId =
+ BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, rawSender);
+ String selfId =
+ BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, self);
+ MessageData messageData = MessageData.createReceivedSmsMessage(messageUri,
+ conversationId, participantId, selfId, message, null /*subject*/, sentAt,
+ receivedAt, seen, read);
+ messageData.setProviderId(MessageData.PROVIDER_WHISPER_PUSH);
+
+ BugleDatabaseOperations.insertNewMessageInTransaction(db, messageData);
+
+ BugleDatabaseOperations.updateConversationMetadataInTransaction(db, conversationId,
+ messageData.getMessageId(), messageData.getReceivedTimeStamp(), blocked,
+ null /*conversationServiceCenter*/, true /* shouldAutoSwitchSelfId */);
+
+ ParticipantData senderData = ParticipantData.getFromId(db, participantId);
+ BugleActionToasts.onMessageReceived(conversationId, senderData, messageData);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+// ProcessPendingMessagesAction.scheduleProcessPendingMessagesAction(false, this); //FIXME ?
+
+ showNotification(conversationId);
+ }
+
+ @Override
+ public void storeIncomingMultimediaMessage(String sender, String message,
+ List<Pair<byte[], Uri>> attachments,
+ long sentAt) {
+ storeIncomingGroupMessage(sender, message, attachments, sentAt, -1);
+ }
+
+ @Override
+ public void storeIncomingGroupMessage(String sender, String message,
+ List<Pair<byte[], Uri>> attachments,
+ long sentAt, long threadId) {
+ try {
+ Uri messageUri = createMessageUri(sender, message, attachments, threadId);
+
+ // Inform sync that message has been added at local received timestamp
+ final SyncManager syncManager = DataModel.get().getSyncManager();
+ syncManager.onNewMessageInserted(sentAt);
+
+ final DatabaseMessages.MmsMessage mms = MmsUtils.loadMms(messageUri);
+
+ if (mms != null) {
+ saveInboxMmsMessage(mms, sender, sentAt);
+ }
+ } catch (InvalidHeaderValueException e) {
+ Log.e(TAG, "storeIncomingMultimediaMessage failed", e);
+ } catch (MmsException e) {
+ Log.e(TAG, "storeIncomingMultimediaMessage failed", e);
+ }
+ }
+
+ @Override
+ public long getThreadId(Set<String> recipients) {
+ return MmsSmsUtils.Threads.getOrCreateThreadId(mContext, recipients);
+ }
+
+ @Override
+ public Set<String> getRecipientsByThread(long threadId) {
+ return new HashSet<>(MmsUtils.getRecipientsByThread(threadId));
+ }
+
+ @Override
+ public Uri persistPart(byte[] contentType, byte[] data, long threadId) {
+ PduPersister pduPersister = PduPersister.getPduPersister(mContext);
+ PduPart pduPart = new PduPart();
+ pduPart.setContentType(contentType);
+ pduPart.setData(data);
+ try {
+ return pduPersister.persistPart(pduPart, threadId, null);
+ } catch (MmsException e) {
+ Log.e(TAG, "persistPart failed", e);
+ return null;
+ }
+ }
+
+ // Show a notification to let the user know a new message has arrived
+ private void showNotification(String conversationId) {
+ BugleNotifications.update(false/*silent*/, conversationId,
+ BugleNotifications.UPDATE_ALL);
+
+ MessagingContentProvider.notifyMessagesChanged(conversationId);
+ MessagingContentProvider.notifyPartsChanged();
+ }
+
+ private Uri createMessageUri(String sender, String message,
+ List<Pair<byte[], Uri>> attachments,
+ long threadId)
+ throws MmsException {
+ PduBody pduBody = new PduBody();
+ if (!TextUtils.isEmpty(message)) {
+ PduPart pduPart = new PduPart();
+ pduPart.setContentType(Util.toIsoBytes(ContentType.TEXT_PLAIN));
+ pduPart.setCharset(CharacterSets.UTF_8);
+ pduPart.setData(message.getBytes());
+ pduBody.addPart(pduPart);
+ }
+
+ for (Pair<byte[], Uri> attachment : attachments) {
+ PduPart pduPart = new PduPart();
+ pduPart.setContentType(attachment.first);
+ pduPart.setDataUri(attachment.second);
+ pduBody.addPart(pduPart);
+ }
+
+ RetrieveConf retrieveConf = new RetrieveConf();
+ retrieveConf.setFrom(new EncodedStringValue(sender));
+ retrieveConf.setBody(pduBody);
+
+ PduPersister persister = PduPersister.getPduPersister(mContext);
+ Uri messageUri = persister.persist(retrieveConf,
+ Telephony.Mms.Inbox.CONTENT_URI,
+ SUBSCRIPTION_ID, null, null);
+
+ if (threadId != -1) {
+ ContentValues values = new ContentValues();
+ values.put(Telephony.Mms.THREAD_ID, threadId);
+ SqliteWrapper.update(mContext, mContext.getContentResolver(),
+ messageUri, values, null, null);
+ }
+
+ return messageUri;
+ }
+
+ private void saveInboxMmsMessage(DatabaseMessages.MmsMessage mms, String from, long sentAt) {
+ int subId = SUBSCRIPTION_ID;
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+
+ final ParticipantData self = BugleDatabaseOperations.getOrCreateSelf(db, subId);
+ if (from == null) {
+ LogUtil.w("WhisperPushMessagingBridge", "Received an MMS without sender address; using unknown sender.");
+ from = ParticipantData.getUnknownSenderDestination();
+ }
+ final ParticipantData rawSender = ParticipantData.getFromRawPhoneBySimLocale(
+ from, subId);
+ final boolean blocked = BugleDatabaseOperations.isBlockedDestination(
+ db, rawSender.getNormalizedDestination());
+ final String conversationId =
+ BugleDatabaseOperations.getOrCreateConversationFromThreadId(db, mms.mThreadId,
+ blocked, subId);
+
+ boolean messageInFocusedConversation =
+ DataModel.get().isFocusedConversation(conversationId);
+ boolean messageInObservableConversation =
+ DataModel.get().isNewMessageObservable(conversationId);
+ mms.mRead = messageInFocusedConversation;
+ mms.mSeen = messageInObservableConversation;
+
+ // Write received placeholder message to our DB
+ db.beginTransaction();
+ try {
+ final String participantId =
+ BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, rawSender);
+ final String selfId =
+ BugleDatabaseOperations.getOrCreateParticipantInTransaction(db, self);
+
+ MessageData messageData = MmsUtils.createMmsMessage(mms, conversationId, participantId,
+ selfId, MessageData.BUGLE_STATUS_INCOMING_COMPLETE);
+ messageData.setProviderId(MessageData.PROVIDER_WHISPER_PUSH);
+ // Write the message
+ BugleDatabaseOperations.insertNewMessageInTransaction(db, messageData);
+
+ ContentValues values = new ContentValues(1);
+ values.put(Telephony.Mms.Part.MSG_ID, messageData.getMessageId());
+ SqliteWrapper.update(mContext, mContext.getContentResolver(),
+ Uri.parse("content://mms/" + sentAt + "/part"),
+ values, null, null);
+
+ BugleDatabaseOperations.updateConversationMetadataInTransaction(db,
+ conversationId, messageData.getMessageId(), messageData.getReceivedTimeStamp(),
+ blocked, true);
+ final ParticipantData senderData = ParticipantData .getFromId(db, participantId);
+ BugleActionToasts.onMessageReceived(conversationId, senderData, messageData);
+ // else update the conversation once we have downloaded final message (or failed)
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+
+ showNotification(conversationId);
+ }
+
+ @Override
+ public boolean isAddressBlacklisted(String address) {
+ DatabaseWrapper db = DataModel.get().getDatabase();
+ ParticipantData rawSender = ParticipantData.getFromRawPhoneBySimLocale(address, SUBSCRIPTION_ID);
+ return BugleDatabaseOperations.isBlockedDestination(db, rawSender.getNormalizedDestination());
+ }
+
+}
diff --git a/src/com/android/messaging/datamodel/BugleDatabaseOperations.java b/src/com/android/messaging/datamodel/BugleDatabaseOperations.java
index 8c40177..9fe5a19 100644
--- a/src/com/android/messaging/datamodel/BugleDatabaseOperations.java
+++ b/src/com/android/messaging/datamodel/BugleDatabaseOperations.java
@@ -25,6 +25,7 @@ import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.support.v4.util.ArrayMap;
import android.support.v4.util.SimpleArrayMap;
+import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import com.android.messaging.Factory;
@@ -317,7 +318,23 @@ public class BugleDatabaseOperations {
@DoesNotRunOnMainThread
public static boolean isBlockedDestination(final DatabaseWrapper db, final String destination) {
Assert.isNotMainThread();
- return isBlockedParticipant(db, destination, ParticipantColumns.NORMALIZED_DESTINATION);
+
+ // do not use isBlockedParticipant(final DatabaseWrapper db, final String value, final String column)
+ // it doesn't compare the same numbers in different formats
+ Cursor cursor = db.query(DatabaseHelper.PARTICIPANTS_TABLE,
+ new String[] { ParticipantColumns.NORMALIZED_DESTINATION },
+ ParticipantColumns.BLOCKED + "=1", null,null, null, null);
+ try {
+ while (cursor.moveToNext()) {
+ String blockedNumber = cursor.getString(0);
+ if (PhoneNumberUtils.compare(destination, blockedNumber)) {
+ return true;
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+ return false;
}
static boolean isBlockedParticipant(final DatabaseWrapper db, final String participantId) {
@@ -1486,6 +1503,26 @@ public class BugleDatabaseOperations {
}
}
+ /**
+ * @return participant count or -1 if conversation doesn't exist
+ */
+ public static int getConversationParticipantCount(final DatabaseWrapper dbWrapper,
+ final String conversationId) {
+ Cursor cursor = dbWrapper.query(DatabaseHelper.CONVERSATIONS_TABLE,
+ new String[] { ConversationColumns.PARTICIPANT_COUNT },
+ ConversationColumns._ID + "=?",
+ new String[] { conversationId },
+ null, null, null);
+ try {
+ if (cursor.moveToFirst()) {
+ return cursor.getInt(0);
+ }
+ } finally {
+ cursor.close();
+ }
+ return -1;
+ }
+
/** Preserve parts in message but clear the stored draft */
public static final int UPDATE_MODE_CLEAR_DRAFT = 1;
/** Add the message as a draft */
diff --git a/src/com/android/messaging/datamodel/DatabaseHelper.java b/src/com/android/messaging/datamodel/DatabaseHelper.java
index f16bb3c..17c487b 100644
--- a/src/com/android/messaging/datamodel/DatabaseHelper.java
+++ b/src/com/android/messaging/datamodel/DatabaseHelper.java
@@ -260,6 +260,9 @@ public class DatabaseHelper extends SQLiteOpenHelper {
/* The detailed status (RESPONSE_STATUS or RETRIEVE_STATUS) for MMS message */
public static final String RAW_TELEPHONY_STATUS = "raw_status";
+
+ /* The identity of a message provider. See com.android.messaging.datamodel.data.MessageData.PROVIDER_* */
+ public static final String PROVIDER_ID = "provider_id";
}
// Messages table SQL
@@ -284,6 +287,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
+ MessageColumns.RAW_TELEPHONY_STATUS + " INT DEFAULT(0), "
+ MessageColumns.SELF_PARTICIPANT_ID + " INT, "
+ MessageColumns.RETRY_START_TIMESTAMP + " INT DEFAULT(0), "
+ + MessageColumns.PROVIDER_ID + " INT DEFAULT(" + MessageData.PROVIDER_DEFAULT + "), "
+ "FOREIGN KEY (" + MessageColumns.CONVERSATION_ID + ") REFERENCES "
+ CONVERSATIONS_TABLE + "(" + ConversationColumns._ID + ") ON DELETE CASCADE "
+ "FOREIGN KEY (" + MessageColumns.SENDER_PARTICIPANT_ID + ") REFERENCES "
diff --git a/src/com/android/messaging/datamodel/DatabaseUpgradeHelper.java b/src/com/android/messaging/datamodel/DatabaseUpgradeHelper.java
index d112533..d5c1f65 100644
--- a/src/com/android/messaging/datamodel/DatabaseUpgradeHelper.java
+++ b/src/com/android/messaging/datamodel/DatabaseUpgradeHelper.java
@@ -17,9 +17,13 @@ package com.android.messaging.datamodel;
import android.database.sqlite.SQLiteDatabase;
+import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
+import com.android.messaging.datamodel.data.MessageData;
import com.android.messaging.util.Assert;
import com.android.messaging.util.LogUtil;
+import static com.android.messaging.datamodel.DatabaseHelper.MESSAGES_TABLE;
+
public class DatabaseUpgradeHelper {
private static final String TAG = LogUtil.BUGLE_DATABASE_TAG;
@@ -29,6 +33,12 @@ public class DatabaseUpgradeHelper {
return;
}
+ if (oldVersion < 2) {
+ db.execSQL("ALTER TABLE " + MESSAGES_TABLE
+ + " ADD COLUMN " + MessageColumns.PROVIDER_ID
+ + " INT DEFAULT(" + MessageData.PROVIDER_DEFAULT + ")");
+ }
+
LogUtil.i(TAG, "Database upgrade started from version " + oldVersion + " to " + newVersion);
// Add future upgrade code here
diff --git a/src/com/android/messaging/datamodel/action/InsertNewMessageAction.java b/src/com/android/messaging/datamodel/action/InsertNewMessageAction.java
index 2567ca9..0166ef1 100644
--- a/src/com/android/messaging/datamodel/action/InsertNewMessageAction.java
+++ b/src/com/android/messaging/datamodel/action/InsertNewMessageAction.java
@@ -39,6 +39,8 @@ import com.android.messaging.util.LogUtil;
import com.android.messaging.util.OsUtil;
import com.android.messaging.util.PhoneUtils;
+import org.whispersystems.whisperpush.WhisperPush;
+
import java.util.ArrayList;
import java.util.List;
@@ -95,7 +97,7 @@ public class InsertNewMessageAction extends Action implements Parcelable {
actionParameters.putParcelable(KEY_MESSAGE, message);
}
- private InsertNewMessageAction(final MessageData message, final int subId) {
+ private InsertNewMessageAction(final MessageData message, int subId) {
super();
actionParameters.putParcelable(KEY_MESSAGE, message);
actionParameters.putInt(KEY_SUB_ID, subId);
@@ -135,6 +137,13 @@ public class InsertNewMessageAction extends Action implements Parcelable {
if (self == null) {
return null;
}
+
+ Context context = Factory.get().getApplicationContext();
+ WhisperPush whisperPush = WhisperPush.getInstance(context);
+ if (whisperPush.isSecureMessagingActive() && message.getIsTransportSecured()) {
+ self.setSubId(0);
+ }
+
message.bindSelfId(self.getId());
// If the user taps the Send button before the conversation draft is created/loaded by
// ReadDraftDataAction (maybe the action service thread was busy), the MessageData may not
@@ -390,6 +399,7 @@ public class InsertNewMessageAction extends Action implements Parcelable {
try {
message = MessageData.createDraftSmsMessage(conversationId,
content.getSelfId(), messageText);
+ message.setProviderId(content.getProviderId());
message.updateSendingMessage(conversationId, messageUri, timestamp);
BugleDatabaseOperations.insertNewMessageInTransaction(db, message);
diff --git a/src/com/android/messaging/datamodel/action/ProcessSentMessageAction.java b/src/com/android/messaging/datamodel/action/ProcessSentMessageAction.java
index f408e47..2fff28b 100644
--- a/src/com/android/messaging/datamodel/action/ProcessSentMessageAction.java
+++ b/src/com/android/messaging/datamodel/action/ProcessSentMessageAction.java
@@ -68,6 +68,7 @@ public class ProcessSentMessageAction extends Action {
private static final String KEY_CONTENT_URI = "content_uri";
private static final String KEY_RESPONSE = "response";
private static final String KEY_RESPONSE_IMPORTANT = "response_important";
+ private static final String KEY_IS_SECURE = "is_secure";
// These are set for messages we sent ourself (legacy), or which we fast-failed before sending.
private static final String KEY_STATUS = "status";
@@ -78,8 +79,10 @@ public class ProcessSentMessageAction extends Action {
final Bundle extras) {
final ProcessSentMessageAction action = new ProcessSentMessageAction();
final Bundle params = action.actionParameters;
+ final boolean isSecure = extras.getBoolean(SendMessageAction.EXTRA_IS_SECURE);
params.putBoolean(KEY_SMS, false);
- params.putBoolean(KEY_SENT_BY_PLATFORM, true);
+ params.putBoolean(KEY_SENT_BY_PLATFORM, !isSecure);
+ params.putBoolean(KEY_IS_SECURE, isSecure);
params.putString(KEY_MESSAGE_ID, extras.getString(SendMessageAction.EXTRA_MESSAGE_ID));
params.putParcelable(KEY_MESSAGE_URI, messageUri);
params.putParcelable(KEY_UPDATED_MESSAGE_URI,
@@ -129,6 +132,8 @@ public class ProcessSentMessageAction extends Action {
final Uri updatedMessageUri = actionParameters.getParcelable(KEY_UPDATED_MESSAGE_URI);
final boolean isSms = actionParameters.getBoolean(KEY_SMS);
final boolean sentByPlatform = actionParameters.getBoolean(KEY_SENT_BY_PLATFORM);
+ final int resultCode = actionParameters.getInt(KEY_RESULT_CODE);
+ final boolean isSecure = actionParameters.getBoolean(KEY_IS_SECURE);
int status = actionParameters.getInt(KEY_STATUS, MmsUtils.MMS_REQUEST_MANUAL_RETRY);
int rawStatus = actionParameters.getInt(KEY_RAW_STATUS,
@@ -150,7 +155,6 @@ public class ProcessSentMessageAction extends Action {
}
}
- final int resultCode = actionParameters.getInt(KEY_RESULT_CODE);
final boolean responseImportant = actionParameters.getBoolean(KEY_RESPONSE_IMPORTANT);
if (resultCode == Activity.RESULT_OK) {
if (responseImportant) {
@@ -182,9 +186,16 @@ public class ProcessSentMessageAction extends Action {
}
}
}
+ } else if (isSecure) {
+ if (resultCode == Activity.RESULT_OK) {
+ status = MmsUtils.MMS_REQUEST_SUCCEEDED;
+ } else if (resultCode == SmsManager.MMS_ERROR_IO_ERROR) {
+ status = MmsUtils.MMS_REQUEST_AUTO_RETRY;
+ } else {
+ status = MmsUtils.MMS_REQUEST_MANUAL_RETRY;
+ }
}
if (messageId != null) {
- final int resultCode = actionParameters.getInt(KEY_RESULT_CODE);
final int httpStatusCode = actionParameters.getInt(KEY_HTTP_STATUS_CODE);
processResult(
messageId, updatedMessageUri, status, rawStatus, isSms, this, subId,
@@ -219,6 +230,7 @@ public class ProcessSentMessageAction extends Action {
update.updateMessageId(message.getMessageId());
// Update image sizes.
update.updateSizesForImageParts();
+ update.setProviderId(message.getProviderId());
// Temp attachments are no longer needed
for (final MessagePartData part : message.getParts()) {
part.destroySync();
diff --git a/src/com/android/messaging/datamodel/action/SendMessageAction.java b/src/com/android/messaging/datamodel/action/SendMessageAction.java
index d7ebe8f..4490bd7 100644
--- a/src/com/android/messaging/datamodel/action/SendMessageAction.java
+++ b/src/com/android/messaging/datamodel/action/SendMessageAction.java
@@ -16,6 +16,7 @@
package com.android.messaging.datamodel.action;
+import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.net.Uri;
@@ -24,6 +25,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.provider.Telephony.Mms;
import android.provider.Telephony.Sms;
+import android.support.v7.mms.MmsManager;
import com.android.messaging.Factory;
import com.android.messaging.datamodel.BugleDatabaseOperations;
@@ -34,10 +36,13 @@ import com.android.messaging.datamodel.MessagingContentProvider;
import com.android.messaging.datamodel.SyncManager;
import com.android.messaging.datamodel.data.MessageData;
import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.metrics.MetricsTracker;
import com.android.messaging.sms.MmsUtils;
import com.android.messaging.util.Assert;
import com.android.messaging.util.LogUtil;
+import org.whispersystems.whisperpush.WhisperPush;
+
import java.util.ArrayList;
/**
@@ -82,6 +87,7 @@ public class SendMessageAction extends Action implements Parcelable {
public static final String EXTRA_UPDATED_MESSAGE_URI = "updated_message_uri";
public static final String EXTRA_CONTENT_URI = "content_uri";
public static final String EXTRA_RESPONSE_IMPORTANT = "response_important";
+ public static final String EXTRA_IS_SECURE = "is_secure";
/**
* Constructor used for retrying sending in the background (only message id available)
@@ -106,6 +112,7 @@ public class SendMessageAction extends Action implements Parcelable {
final ParticipantData self = BugleDatabaseOperations.getExistingParticipant(
db, message.getSelfId());
+
final Uri messageUri = message.getSmsMessageUri();
final String conversationId = message.getConversationId();
@@ -187,26 +194,51 @@ public class SendMessageAction extends Action implements Parcelable {
Uri messageUri = actionParameters.getParcelable(KEY_MESSAGE_URI);
Uri updatedMessageUri = null;
final boolean isSms = message.getProtocol() == MessageData.PROTOCOL_SMS;
- final int subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
final String subPhoneNumber = actionParameters.getString(KEY_SUB_PHONE_NUMBER);
LogUtil.i(TAG, "SendMessageAction: Sending " + (isSms ? "SMS" : "MMS") + " message "
+ messageId + " in conversation " + message.getConversationId());
+ final Context context = Factory.get().getApplicationContext();
+ final WhisperPush whisperPush = WhisperPush.getInstance(context);
int status;
int rawStatus = MessageData.RAW_TELEPHONY_STATUS_UNDEFINED;
int resultCode = MessageData.UNKNOWN_RESULT_CODE;
+ int subId;
+ boolean isSecured = message.getIsTransportSecured();
+ if (isSecured && whisperPush.isSecureMessagingActive()) {
+ subId = 0;
+ }
+ else {
+ if (isSecured) {
+ // fallback to traditional SMS/MMS
+ isSecured = false;
+ message.setTransportSecured(false);
+ final DatabaseWrapper db = DataModel.get().getDatabase();
+ ContentValues values = new ContentValues(1);
+ values.put(MessageColumns.PROVIDER_ID, message.getProviderId());
+ BugleDatabaseOperations.updateMessageRowIfExists(db, messageId, values);
+ }
+ subId = actionParameters.getInt(KEY_SUB_ID, ParticipantData.DEFAULT_SELF_SUB_ID);
+ if (subId == 0) {
+ subId = MmsManager.DEFAULT_SUB_ID;
+ }
+ }
if (isSms) {
- Assert.notNull(messageUri);
final String recipient = actionParameters.getString(KEY_RECIPIENT);
final String messageText = message.getMessageText();
- final String smsServiceCenter = actionParameters.getString(KEY_SMS_SERVICE_CENTER);
- final boolean deliveryReportRequired = MmsUtils.isDeliveryReportRequired(subId);
-
- status = MmsUtils.sendSmsMessage(recipient, messageText, messageUri, subId,
- smsServiceCenter, deliveryReportRequired);
+ if (isSecured) {
+ status = MmsUtils.sendSecureSmsMessage(recipient, messageText,
+ message.getReceivedTimeStamp());
+ }
+ else {
+ Assert.notNull(messageUri);
+ final String smsServiceCenter = actionParameters.getString(KEY_SMS_SERVICE_CENTER);
+ final boolean deliveryReportRequired = MmsUtils.isDeliveryReportRequired(subId);
+ status = MmsUtils.sendSmsMessage(recipient, messageText, messageUri, subId,
+ smsServiceCenter, deliveryReportRequired);
+ }
} else {
- final Context context = Factory.get().getApplicationContext();
final ArrayList<String> recipients =
actionParameters.getStringArrayList(KEY_RECIPIENTS);
if (messageUri == null) {
@@ -235,8 +267,14 @@ public class SendMessageAction extends Action implements Parcelable {
final Bundle extras = new Bundle();
extras.putString(EXTRA_MESSAGE_ID, messageId);
extras.putParcelable(EXTRA_UPDATED_MESSAGE_URI, updatedMessageUri);
- final MmsUtils.StatusPlusUri result = MmsUtils.sendMmsMessage(context, subId,
- messageUri, extras);
+ MmsUtils.StatusPlusUri result;
+ if (isSecured) {
+ result = MmsUtils.sendSecureMmsMessage(context,
+ messageUri, message, recipients, extras);
+ } else {
+ result= MmsUtils.sendMmsMessage(context, subId,
+ messageUri, extras);
+ }
if (result == MmsUtils.STATUS_PENDING) {
// Async send, so no status yet
LogUtil.d(TAG, "SendMessageAction: Sending MMS message " + messageId
@@ -300,6 +338,19 @@ public class SendMessageAction extends Action implements Parcelable {
return null;
}
+ private static void trackMmsSmsSent(Context context, String conversationId, boolean isSms,
+ boolean sentSuccess) {
+ MetricsTracker metricsTracker = MetricsTracker.getInstance(context);
+ if (isSms) {
+ metricsTracker.trackSmsSentAsync(sentSuccess);
+ } else {
+ DatabaseWrapper db = DataModel.get().getDatabase();
+ int participantCount = BugleDatabaseOperations.getConversationParticipantCount(db,
+ conversationId);
+ metricsTracker.trackMmsSentAsync(sentSuccess, participantCount >= 2);
+ }
+ }
+
/**
* Update the message status (and message itself if necessary)
* @param isSms whether this is an SMS or MMS
@@ -329,6 +380,7 @@ public class SendMessageAction extends Action implements Parcelable {
case MessageData.BUGLE_STATUS_OUTGOING_DELIVERED:
type = Sms.MESSAGE_TYPE_SENT;
messageBox = Mms.MESSAGE_BOX_SENT;
+ trackMmsSmsSent(context, message.getConversationId(), isSms, true);
break;
case MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND:
case MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY:
@@ -344,6 +396,7 @@ public class SendMessageAction extends Action implements Parcelable {
case MessageData.BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER:
type = Sms.MESSAGE_TYPE_FAILED;
messageBox = Mms.MESSAGE_BOX_FAILED;
+ trackMmsSmsSent(context, message.getConversationId(), isSms, false);
break;
default:
type = Sms.MESSAGE_TYPE_ALL;
diff --git a/src/com/android/messaging/datamodel/data/ConversationMessageData.java b/src/com/android/messaging/datamodel/data/ConversationMessageData.java
index 19e1b97..d569ee1 100644
--- a/src/com/android/messaging/datamodel/data/ConversationMessageData.java
+++ b/src/com/android/messaging/datamodel/data/ConversationMessageData.java
@@ -59,6 +59,7 @@ public class ConversationMessageData {
private long mReceivedTimestamp;
private boolean mSeen;
private boolean mRead;
+ private int mProviderId;
private int mProtocol;
private int mStatus;
private String mSmsMessageUri;
@@ -103,6 +104,7 @@ public class ConversationMessageData {
mReceivedTimestamp = cursor.getLong(INDEX_RECEIVED_TIMESTAMP);
mSeen = (cursor.getInt(INDEX_SEEN) != 0);
mRead = (cursor.getInt(INDEX_READ) != 0);
+ mProviderId = cursor.getInt(INDEX_PROVIDER_ID);
mProtocol = cursor.getInt(INDEX_PROTOCOL);
mStatus = cursor.getInt(INDEX_STATUS);
mSmsMessageUri = cursor.getString(INDEX_SMS_MESSAGE_URI);
@@ -464,6 +466,10 @@ public class ConversationMessageData {
return mRead;
}
+ public boolean getIsSecured() {
+ return MessageData.isProviderSecure(mProviderId);
+ }
+
public final boolean getIsMms() {
return (mProtocol == MessageData.PROTOCOL_MMS ||
mProtocol == MessageData.PROTOCOL_MMS_PUSH_NOTIFICATION);
@@ -764,7 +770,9 @@ public class ConversationMessageData {
+ DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.CONTACT_ID
+ " as " + ConversationMessageViewColumns.SENDER_CONTACT_ID + ", "
+ DatabaseHelper.PARTICIPANTS_TABLE + '.' + ParticipantColumns.LOOKUP_KEY
- + " as " + ConversationMessageViewColumns.SENDER_CONTACT_LOOKUP_KEY + " ";
+ + " as " + ConversationMessageViewColumns.SENDER_CONTACT_LOOKUP_KEY + ", "
+ + DatabaseHelper.MESSAGES_TABLE + '.' + MessageColumns.PROVIDER_ID
+ + " as " + ConversationMessageViewColumns.PROVIDER_ID + " ";
private static final String CONVERSATION_MESSAGES_QUERY_FROM_WHERE_SQL =
" FROM " + DatabaseHelper.MESSAGES_TABLE
@@ -837,6 +845,7 @@ public class ConversationMessageData {
static final String PARTS_WIDTHS = "parts_widths";
static final String PARTS_HEIGHTS = "parts_heights";
static final String PARTS_TEXTS = "parts_texts";
+ static final String PROVIDER_ID = MessageColumns.PROVIDER_ID;
}
private static int sIndexIncrementer = 0;
@@ -874,6 +883,7 @@ public class ConversationMessageData {
private static final int INDEX_SENDER_PROFILE_PHOTO_URI = sIndexIncrementer++;
private static final int INDEX_SENDER_CONTACT_ID = sIndexIncrementer++;
private static final int INDEX_SENDER_CONTACT_LOOKUP_KEY = sIndexIncrementer++;
+ private static final int INDEX_PROVIDER_ID = sIndexIncrementer++;
private static String[] sProjection = {
@@ -909,6 +919,7 @@ public class ConversationMessageData {
ConversationMessageViewColumns.SENDER_PROFILE_PHOTO_URI,
ConversationMessageViewColumns.SENDER_CONTACT_ID,
ConversationMessageViewColumns.SENDER_CONTACT_LOOKUP_KEY,
+ ConversationMessageViewColumns.PROVIDER_ID
};
public static String[] getProjection() {
diff --git a/src/com/android/messaging/datamodel/data/ConversationParticipantsData.java b/src/com/android/messaging/datamodel/data/ConversationParticipantsData.java
index 0b5ef51..579e65b 100644
--- a/src/com/android/messaging/datamodel/data/ConversationParticipantsData.java
+++ b/src/com/android/messaging/datamodel/data/ConversationParticipantsData.java
@@ -61,7 +61,7 @@ public class ConversationParticipantsData implements Iterable<ParticipantData> {
return mConversationParticipantsMap.get(participantId);
}
- ArrayList<ParticipantData> getParticipantListExcludingSelf() {
+ public ArrayList<ParticipantData> getParticipantListExcludingSelf() {
final ArrayList<ParticipantData> retList =
new ArrayList<ParticipantData>(mConversationParticipantsMap.size());
for (int i = 0; i < mConversationParticipantsMap.size(); i++) {
diff --git a/src/com/android/messaging/datamodel/data/DraftMessageData.java b/src/com/android/messaging/datamodel/data/DraftMessageData.java
index 7a7199a..fcdd53b 100644
--- a/src/com/android/messaging/datamodel/data/DraftMessageData.java
+++ b/src/com/android/messaging/datamodel/data/DraftMessageData.java
@@ -38,6 +38,7 @@ import com.android.messaging.util.BugleGservicesKeys;
import com.android.messaging.util.LogUtil;
import com.android.messaging.util.PhoneUtils;
import com.android.messaging.util.SafeAsyncTask;
+import com.android.messaging.util.SecureMessagingHelper;
import java.util.ArrayList;
import java.util.Collection;
@@ -93,6 +94,7 @@ public class DraftMessageData extends BindableData implements ReadDraftDataActio
private String mSelfId;
private MessageTextStats mMessageTextStats;
private boolean mSending;
+ private boolean mSecured;
/** Keeps track of completed attachments in the message draft. This data is persisted to db */
private final List<MessagePartData> mAttachments;
@@ -190,6 +192,7 @@ public class DraftMessageData extends BindableData implements ReadDraftDataActio
message = MessageData.createDraftSmsMessage(mConversationId, mSelfId,
mMessageText);
}
+ message.setTransportSecured(isSecured());
if (clearLocalCopy) {
// The message now owns all the attachments and the text...
@@ -606,6 +609,12 @@ public class DraftMessageData extends BindableData implements ReadDraftDataActio
return mSending;
}
+ public boolean isSecured() {return mSecured;}
+
+ public void setSecured(boolean secured) {
+ mSecured = secured;
+ }
+
@Override // ReadDraftMessageActionListener.onReadDraftMessageSucceeded
public void onReadDraftDataSucceeded(final ReadDraftDataAction action, final Object data,
final MessageData message, final ConversationListItemData conversation) {
@@ -697,8 +706,9 @@ public class DraftMessageData extends BindableData implements ReadDraftDataActio
}
public void checkDraftForAction(final boolean checkMessageSize, final int selfSubId,
- final CheckDraftTaskCallback callback, final Binding<DraftMessageData> binding) {
- new CheckDraftForSendTask(checkMessageSize, selfSubId, callback, binding)
+ SecureMessagingHelper helper, final CheckDraftTaskCallback callback,
+ final Binding<DraftMessageData> binding) {
+ new CheckDraftForSendTask(checkMessageSize, selfSubId, helper, callback, binding)
.executeOnThreadPool((Void) null);
}
@@ -748,17 +758,21 @@ public class DraftMessageData extends BindableData implements ReadDraftDataActio
public static final int RESULT_MESSAGE_OVER_LIMIT = 3;
public static final int RESULT_VIDEO_ATTACHMENT_LIMIT_EXCEEDED = 4;
public static final int RESULT_SIM_NOT_READY = 5;
+ public static final int RESULT_PARTICIPANTS_MIXED = 6;
private final boolean mCheckMessageSize;
private final int mSelfSubId;
private final CheckDraftTaskCallback mCallback;
+ private SecureMessagingHelper mSecureMessagingHelper;
private final String mBindingId;
private final List<MessagePartData> mAttachmentsCopy;
private int mPreExecuteResult = RESULT_PASSED;
public CheckDraftForSendTask(final boolean checkMessageSize, final int selfSubId,
- final CheckDraftTaskCallback callback, final Binding<DraftMessageData> binding) {
+ SecureMessagingHelper secureMessagingHelperelper, final CheckDraftTaskCallback callback,
+ final Binding<DraftMessageData> binding) {
mCheckMessageSize = checkMessageSize;
mSelfSubId = selfSubId;
+ mSecureMessagingHelper = secureMessagingHelperelper;
mCallback = callback;
mBindingId = binding.getBindingId();
// Obtain an immutable copy of the attachment list so we can operate on it in the
@@ -803,6 +817,15 @@ public class DraftMessageData extends BindableData implements ReadDraftDataActio
if (mCheckMessageSize && getIsMessageOverLimit()) {
return RESULT_MESSAGE_OVER_LIMIT;
}
+
+ if (mSecureMessagingHelper != null) {
+ mSecureMessagingHelper.checkIfAllParticipantsSecured();
+ setSecured(mSecureMessagingHelper.isAllParticipantsSecured());
+ if (mSecureMessagingHelper.isParticipantsMixed()) {
+ return RESULT_PARTICIPANTS_MIXED;
+ }
+ }
+
return RESULT_PASSED;
}
diff --git a/src/com/android/messaging/datamodel/data/MessageData.java b/src/com/android/messaging/datamodel/data/MessageData.java
index a3698a9..a7ea453 100644
--- a/src/com/android/messaging/datamodel/data/MessageData.java
+++ b/src/com/android/messaging/datamodel/data/MessageData.java
@@ -60,6 +60,7 @@ public class MessageData implements Parcelable {
MessageColumns.MMS_EXPIRY,
MessageColumns.RAW_TELEPHONY_STATUS,
MessageColumns.RETRY_START_TIMESTAMP,
+ MessageColumns.PROVIDER_ID,
};
private static final int INDEX_ID = 0;
@@ -81,13 +82,13 @@ public class MessageData implements Parcelable {
private static final int INDEX_MMS_EXPIRY = 16;
private static final int INDEX_RAW_TELEPHONY_STATUS = 17;
private static final int INDEX_RETRY_START_TIMESTAMP = 18;
+ private static final int INDEX_PROVIDER_ID = 19;
// SQL statement to insert a "complete" message row (columns based on the projection above).
private static final String INSERT_MESSAGE_SQL =
"INSERT INTO " + DatabaseHelper.MESSAGES_TABLE + " ( "
- + TextUtils.join(", ", Arrays.copyOfRange(sProjection, 1,
- INDEX_RETRY_START_TIMESTAMP + 1))
- + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+ + TextUtils.join(", ", Arrays.copyOfRange(sProjection, 1, sProjection.length))
+ + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
private String mMessageId;
private String mConversationId;
@@ -109,6 +110,7 @@ public class MessageData implements Parcelable {
private int mStatus;
private final ArrayList<MessagePartData> mParts;
private long mRetryStartTimestamp;
+ private int mProviderId = PROVIDER_DEFAULT;
// PROTOCOL Values
public static final int PROTOCOL_UNKNOWN = -1; // Unknown type
@@ -116,6 +118,10 @@ public class MessageData implements Parcelable {
public static final int PROTOCOL_MMS = 1; // MMS message
public static final int PROTOCOL_MMS_PUSH_NOTIFICATION = 2; // MMS WAP push notification
+ // PROVIDER_ID Values
+ public static final int PROVIDER_DEFAULT = 0;
+ public static final int PROVIDER_WHISPER_PUSH = 1;
+
// Bugle STATUS Values
public static final int BUGLE_STATUS_UNKNOWN = 0;
@@ -398,6 +404,7 @@ public class MessageData implements Parcelable {
mMmsTransactionId = cursor.getString(INDEX_MMS_TRANSACTION_ID);
mMmsContentLocation = cursor.getString(INDEX_MMS_CONTENT_LOCATION);
mRetryStartTimestamp = cursor.getLong(INDEX_RETRY_START_TIMESTAMP);
+ mProviderId = cursor.getInt(INDEX_PROVIDER_ID);
}
/**
@@ -433,6 +440,7 @@ public class MessageData implements Parcelable {
values.put(MessageColumns.MMS_CONTENT_LOCATION, mMmsContentLocation);
values.put(MessageColumns.RAW_TELEPHONY_STATUS, mRawStatus);
values.put(MessageColumns.RETRY_START_TIMESTAMP, mRetryStartTimestamp);
+ values.put(MessageColumns.PROVIDER_ID, mProviderId);
}
/**
@@ -469,6 +477,7 @@ public class MessageData implements Parcelable {
}
insert.bindLong(INDEX_RAW_TELEPHONY_STATUS, mRawStatus);
insert.bindLong(INDEX_RETRY_START_TIMESTAMP, mRetryStartTimestamp);
+ insert.bindLong(INDEX_PROVIDER_ID, mProviderId);
return insert;
}
@@ -554,6 +563,18 @@ public class MessageData implements Parcelable {
mSeen = hasSeen;
}
+ public boolean getIsTransportSecured() {
+ return isProviderSecure(mProviderId);
+ }
+
+ public void setTransportSecured(final boolean secured) {
+ if (secured) {
+ mProviderId = PROVIDER_WHISPER_PUSH;
+ } else {
+ mProviderId = PROVIDER_DEFAULT;
+ }
+ }
+
public final boolean getInResendWindow(final long now) {
final long maxAgeToResend = BugleGservices.get().getLong(
BugleGservicesKeys.MESSAGE_RESEND_TIMEOUT_MS,
@@ -682,6 +703,18 @@ public class MessageData implements Parcelable {
return text.toString();
}
+ public int getProviderId() {
+ return mProviderId;
+ }
+
+ public void setProviderId(int providerId) {
+ mProviderId = providerId;
+ }
+
+ public static boolean isProviderSecure(int providerId) {
+ return providerId == PROVIDER_WHISPER_PUSH;
+ }
+
/**
* Takes all captions from attachments and adds them as a prefix to the first text part or
* appends a text part
@@ -845,6 +878,7 @@ public class MessageData implements Parcelable {
mMmsContentLocation = in.readString();
mRawStatus = in.readInt();
mRetryStartTimestamp = in.readLong();
+ mProviderId = in.readInt();
// Read parts
mParts = new ArrayList<MessagePartData>();
@@ -881,6 +915,7 @@ public class MessageData implements Parcelable {
dest.writeString(mMmsContentLocation);
dest.writeInt(mRawStatus);
dest.writeLong(mRetryStartTimestamp);
+ dest.writeInt(mProviderId);
// Write parts
dest.writeInt(mParts.size());
diff --git a/src/com/android/messaging/datamodel/data/ParticipantData.java b/src/com/android/messaging/datamodel/data/ParticipantData.java
index 521c354..d32c05d 100644
--- a/src/com/android/messaging/datamodel/data/ParticipantData.java
+++ b/src/com/android/messaging/datamodel/data/ParticipantData.java
@@ -425,6 +425,10 @@ public class ParticipantData implements Parcelable {
mContactDestination = destination;
}
+ public void setSubId(int subId) {
+ mSubId = subId;
+ }
+
public int getSubId() {
return mSubId;
}
diff --git a/src/com/android/messaging/metrics/Event.java b/src/com/android/messaging/metrics/Event.java
new file mode 100644
index 0000000..a0e65df
--- /dev/null
+++ b/src/com/android/messaging/metrics/Event.java
@@ -0,0 +1,25 @@
+package com.android.messaging.metrics;
+
+class Event {
+ static final String DEFAULT_LABEL_NAME = "daily";
+
+ private String category;
+ private String action;
+
+ public Event(String category, String action) {
+ this.category = category;
+ this.action = action;
+ }
+
+ public String getCategory() {
+ return category;
+ }
+
+ public String getAction() {
+ return action;
+ }
+
+ public String getLabel() {
+ return DEFAULT_LABEL_NAME;
+ }
+}
diff --git a/src/com/android/messaging/metrics/EventStatistic.java b/src/com/android/messaging/metrics/EventStatistic.java
new file mode 100644
index 0000000..ea0e3f4
--- /dev/null
+++ b/src/com/android/messaging/metrics/EventStatistic.java
@@ -0,0 +1,62 @@
+package com.android.messaging.metrics;
+
+class EventStatistic {
+ private String category;
+ private String action;
+ private String label;
+ private int value;
+
+ public EventStatistic(String category, String action, String label, int value) {
+ this.category = category;
+ this.action = action;
+ this.label = label;
+ this.value = value;
+ }
+
+ public String getCategory() {
+ return category;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public String getAction() {
+ return action;
+ }
+
+ public int getValue() {
+ return value;
+ }
+
+ public static class Builder {
+ private String category;
+ private String action;
+ private String label;
+ private int value;
+
+ public Builder setCategory(String category) {
+ this.category = category;
+ return this;
+ }
+
+ public Builder setAction(String action) {
+ this.action = action;
+ return this;
+ }
+
+ public Builder setLabel(String label) {
+ this.label = label;
+ return this;
+ }
+
+ public Builder setValue(int value) {
+ this.value = value;
+ return this;
+ }
+
+ public EventStatistic build() {
+ return new EventStatistic(category, action, label, value);
+ }
+ }
+}
diff --git a/src/com/android/messaging/metrics/MetricsDatabase.java b/src/com/android/messaging/metrics/MetricsDatabase.java
new file mode 100644
index 0000000..2a9e209
--- /dev/null
+++ b/src/com/android/messaging/metrics/MetricsDatabase.java
@@ -0,0 +1,103 @@
+package com.android.messaging.metrics;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteStatement;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class MetricsDatabase {
+ private static final String TABLE_NAME = "Metrics";
+ static final String VALUE = "value";
+ static final String CATEGORY = "category";
+ static final String ACTION = "action";
+ static final String LABEL = "label";
+
+ static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME +
+ " (" +
+ CATEGORY + " text," +
+ ACTION + " text," +
+ LABEL + " text," +
+ VALUE + " int," +
+ "CONSTRAINT `main` UNIQUE (" + CATEGORY + ", " + ACTION + ", " + LABEL + ") ON CONFLICT FAIL" +
+ ");";
+
+ private static final String INSERT = "INSERT INTO " + TABLE_NAME + " (" + CATEGORY + ", " +
+ ACTION + ", " + LABEL + ", " + VALUE + ") " +
+ "VALUES (?, ?, ?, 1);\n";
+ private static final String UPDATE = "UPDATE " + TABLE_NAME + " SET " + VALUE + " = " + VALUE + " + 1 WHERE " +
+ CATEGORY + "=? AND " + ACTION + "=? AND " + LABEL + "=?" ;
+
+ private static final String SELECT_STATISTICS = "SELECT " + CATEGORY + ", "
+ + ACTION + ", " + LABEL + ", " + VALUE + " FROM " +
+ TABLE_NAME;
+
+ private static final String CLEAR_STATS = "DELETE FROM " + TABLE_NAME;
+
+ private static volatile MetricsDatabase mInstance;
+
+ public static MetricsDatabase getInstance(Context context) {
+ if (mInstance == null) {
+ synchronized (MetricsDatabase.class) {
+ if (mInstance == null) {
+ mInstance = new MetricsDatabase(context.getApplicationContext());
+ }
+ }
+ }
+ return mInstance;
+ }
+
+ private SQLiteDatabase mDatabase;
+ private SQLiteStatement mInsertStatement;
+ private SQLiteStatement mUpdateStatement;
+
+ private MetricsDatabase(Context context) {
+ mDatabase = new MetricsDatabaseHelper(context).getWritableDatabase();
+ mInsertStatement = mDatabase.compileStatement(INSERT);
+ mUpdateStatement = mDatabase.compileStatement(UPDATE);
+ }
+
+ public synchronized void insert(Event event) {
+ mUpdateStatement.bindAllArgsAsStrings(new String[]{event.getCategory(), event.getAction(), event.getLabel()});
+ if (mUpdateStatement.executeUpdateDelete() <= 0) {
+ mInsertStatement.bindAllArgsAsStrings(new String[]{event.getCategory(), event.getAction(),
+ event.getLabel()});
+ mInsertStatement.executeInsert();
+ }
+ }
+
+ public List<EventStatistic> getAllStatistics() {
+ Cursor cursor = null;
+ try {
+ cursor = mDatabase.rawQuery(SELECT_STATISTICS, null);
+ List<EventStatistic> statistics = new ArrayList<>(cursor.getCount());
+
+ int actionColumnIndex = cursor.getColumnIndex(ACTION);
+ int categoryColumnIndex = cursor.getColumnIndex(CATEGORY);
+ int valueColumnIndex = cursor.getColumnIndex(VALUE);
+ int labelColumnIndex = cursor.getColumnIndex(LABEL);
+
+ while (cursor.moveToNext()) {
+ EventStatistic statistic = new EventStatistic.Builder()
+ .setAction(cursor.getString(actionColumnIndex))
+ .setCategory(cursor.getString(categoryColumnIndex))
+ .setValue(cursor.getInt(valueColumnIndex))
+ .setLabel(cursor.getString(labelColumnIndex))
+ .build();
+ statistics.add(statistic);
+ }
+
+ return statistics;
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ public synchronized void clearStatistics() {
+ mDatabase.execSQL(CLEAR_STATS);
+ }
+}
diff --git a/src/com/android/messaging/metrics/MetricsDatabaseHelper.java b/src/com/android/messaging/metrics/MetricsDatabaseHelper.java
new file mode 100644
index 0000000..54e904b
--- /dev/null
+++ b/src/com/android/messaging/metrics/MetricsDatabaseHelper.java
@@ -0,0 +1,23 @@
+package com.android.messaging.metrics;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+class MetricsDatabaseHelper extends SQLiteOpenHelper {
+ private static final String DATABASE_NAME = "metrics.db";
+ private static final int DATABASE_VERSION = 1;
+
+ public MetricsDatabaseHelper(Context appContext) {
+ super(appContext, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL(MetricsDatabase.CREATE_TABLE);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ }
+}
diff --git a/src/com/android/messaging/metrics/MetricsSendService.java b/src/com/android/messaging/metrics/MetricsSendService.java
new file mode 100644
index 0000000..e63a4db
--- /dev/null
+++ b/src/com/android/messaging/metrics/MetricsSendService.java
@@ -0,0 +1,55 @@
+package com.android.messaging.metrics;
+
+import android.app.AlarmManager;
+import android.app.IntentService;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserHandle;
+
+import java.util.Calendar;
+
+public class MetricsSendService extends IntentService {
+ private static final String ANALYTIC_INTENT = "com.cyngn.stats.action.SEND_ANALYITC_EVENT";
+ private static final String ANALYTIC_PERMISSION = "com.cyngn.stats.SEND_ANALYTICS";
+
+ public MetricsSendService() {
+ super(MetricsSendService.class.getName());
+ }
+
+ public static void schedule(Context context) {
+ PendingIntent intent = PendingIntent.getService(context, 0,
+ new Intent(context, MetricsSendService.class),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTimeInMillis(System.currentTimeMillis());
+ calendar.set(Calendar.SECOND, 0);
+ calendar.set(Calendar.MINUTE, 0);
+ calendar.set(Calendar.HOUR, 0);
+ calendar.set(Calendar.AM_PM, Calendar.AM);
+ calendar.add(Calendar.DAY_OF_MONTH, 1);
+
+ AlarmManager alarmManager = (AlarmManager)context.getSystemService(ALARM_SERVICE);
+ alarmManager.setRepeating(AlarmManager.RTC, calendar.getTimeInMillis(),
+ AlarmManager.INTERVAL_DAY, intent);
+ }
+
+ @Override
+ protected void onHandleIntent(Intent i) {
+ MetricsDatabase metricsDatabase = MetricsDatabase.getInstance(this);
+
+ Intent intent = new Intent();
+ intent.setAction(ANALYTIC_INTENT);
+
+ for (EventStatistic statistic : metricsDatabase.getAllStatistics()) {
+ intent.putExtra(MetricsDatabase.CATEGORY, statistic.getCategory());
+ intent.putExtra(MetricsDatabase.ACTION, statistic.getAction());
+ intent.putExtra(MetricsDatabase.LABEL, statistic.getLabel());
+ intent.putExtra(MetricsDatabase.VALUE, statistic.getValue());
+ sendBroadcast(intent, ANALYTIC_PERMISSION);
+ }
+
+ metricsDatabase.clearStatistics();
+ }
+}
diff --git a/src/com/android/messaging/metrics/MetricsTracker.java b/src/com/android/messaging/metrics/MetricsTracker.java
new file mode 100644
index 0000000..218a4e2
--- /dev/null
+++ b/src/com/android/messaging/metrics/MetricsTracker.java
@@ -0,0 +1,111 @@
+package com.android.messaging.metrics;
+
+import android.content.Context;
+import android.os.AsyncTask;
+import android.util.Log;
+import com.android.messaging.sms.MmsUtils;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+public class MetricsTracker {
+ private static final String SEND_FAILED_ACTION = "send_failed";
+ private static final String SEND_SUCCESS_ACTION = "send_success";
+ private static final String SMS_CATEGORY = "sms";
+ private static final String MMS_CATEGORY = "mms";
+ private static final String MMS_GROUP_CATEGORY = "mms_group";
+
+ public static final String TAG = "Metrics";
+
+ private final Context mContext;
+
+ private static volatile MetricsTracker mInstance;
+ private static final Executor EXECUTOR = AsyncTask.SERIAL_EXECUTOR;
+
+ public static MetricsTracker getInstance(Context context) {
+ if (mInstance == null) {
+ synchronized (MetricsTracker.class) {
+ if (mInstance == null) {
+ mInstance = new MetricsTracker(context.getApplicationContext());
+ }
+ }
+ }
+ return mInstance;
+ }
+
+ private MetricsTracker(Context context) {
+ this.mContext = context.getApplicationContext();
+ }
+
+ private void writeEventAsync(final Event event) {
+ EXECUTOR.execute(new Runnable() {
+ @Override
+ public void run() {
+ writeEvent(event);
+ }
+ });
+ }
+
+ private void writeEvent(Event event) {
+ MetricsDatabase metricsDatabase = MetricsDatabase.getInstance(mContext);
+ metricsDatabase.insert(event);
+ Log.i(TAG, event.toString() + " event saved to database");
+ }
+
+ private void trackSmsSentFailedAsync() {
+ writeEventAsync(new Event(SMS_CATEGORY, SEND_FAILED_ACTION));
+ }
+
+ private void trackSmsSentSuccessAsync() {
+ writeEventAsync(new Event(SMS_CATEGORY, SEND_SUCCESS_ACTION));
+ }
+
+ private void trackMmsSentFailed() {
+ writeEvent(new Event(MMS_CATEGORY, SEND_FAILED_ACTION));
+ }
+
+ private void trackMmsSentSuccess() {
+ writeEvent(new Event(MMS_CATEGORY, SEND_SUCCESS_ACTION));
+ }
+
+ private void trackGroupMmsSentSuccess() {
+ writeEvent(new Event(MMS_GROUP_CATEGORY, SEND_SUCCESS_ACTION));
+ }
+
+ private void trackGroupMmsSentFailed() {
+ writeEvent(new Event(MMS_GROUP_CATEGORY, SEND_FAILED_ACTION));
+ }
+
+ private void trackMmsSent(boolean success, boolean isGroupMessage) {
+ if (isGroupMessage) {
+ if (success) {
+ trackGroupMmsSentSuccess();
+ } else {
+ trackGroupMmsSentFailed();
+ }
+ } else {
+ if (success) {
+ trackMmsSentSuccess();
+ } else {
+ trackMmsSentFailed();
+ }
+ }
+ }
+
+ public void trackMmsSentAsync(final boolean success, final boolean isGroupMessage) {
+ EXECUTOR.execute(new Runnable() {
+ @Override
+ public void run() {
+ trackMmsSent(success, isGroupMessage);
+ }
+ });
+ }
+
+ public void trackSmsSentAsync(boolean success) {
+ if (success) {
+ trackSmsSentSuccessAsync();
+ } else {
+ trackSmsSentFailedAsync();
+ }
+ }
+}
diff --git a/src/com/android/messaging/sms/MmsConfig.java b/src/com/android/messaging/sms/MmsConfig.java
index f13d785..f1b31c2 100755
--- a/src/com/android/messaging/sms/MmsConfig.java
+++ b/src/com/android/messaging/sms/MmsConfig.java
@@ -109,6 +109,11 @@ public class MmsConfig {
* Retrieves the MmsConfig instance associated with the given {@code subId}
*/
public static MmsConfig get(final int subId) {
+ if (subId == 0) {
+ synchronized (sSubIdToMmsConfigMap) {
+ return sFallback;
+ }
+ }
final int realSubId = PhoneUtils.getDefault().getEffectiveSubId(subId);
synchronized (sSubIdToMmsConfigMap) {
final MmsConfig mmsConfig = sSubIdToMmsConfigMap.get(realSubId);
diff --git a/src/com/android/messaging/sms/MmsUtils.java b/src/com/android/messaging/sms/MmsUtils.java
index 913e9a6..3512ea8 100644
--- a/src/com/android/messaging/sms/MmsUtils.java
+++ b/src/com/android/messaging/sms/MmsUtils.java
@@ -16,6 +16,8 @@
package com.android.messaging.sms;
+import android.app.Activity;
+import android.app.PendingIntent;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
@@ -41,6 +43,8 @@ import android.telephony.SmsMessage;
import android.text.TextUtils;
import android.text.util.Rfc822Token;
import android.text.util.Rfc822Tokenizer;
+import android.util.Log;
+import android.util.Pair;
import com.android.messaging.Factory;
import com.android.messaging.R;
@@ -66,6 +70,7 @@ import com.android.messaging.mmslib.pdu.PduPersister;
import com.android.messaging.mmslib.pdu.RetrieveConf;
import com.android.messaging.mmslib.pdu.SendConf;
import com.android.messaging.mmslib.pdu.SendReq;
+import com.android.messaging.receiver.SendStatusReceiver;
import com.android.messaging.sms.SmsSender.SendResult;
import com.android.messaging.util.Assert;
import com.android.messaging.util.BugleGservices;
@@ -82,6 +87,10 @@ import com.android.messaging.util.OsUtil;
import com.android.messaging.util.PhoneUtils;
import com.google.common.base.Joiner;
+import org.whispersystems.whisperpush.WhisperPush;
+import org.whispersystems.whisperpush.api.OutgoingMessage;
+import org.whispersystems.whisperpush.service.WhisperPushMessageSender;
+
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
@@ -2124,6 +2133,63 @@ public class MmsUtils {
return new StatusPlusUri(status, rawStatus, messageUri);
}
+ public static StatusPlusUri sendSecureMmsMessage(final Context context,
+ final Uri messageUri,
+ final MessageData message,
+ final ArrayList<String> recipients,
+ final Bundle extras) {
+ WhisperPush whisperPush = WhisperPush.getInstance(context);
+ if (!whisperPush.isSecureMessagingNetworkAvailable()) {
+ LogUtil.w(TAG, "MmsUtils: can't send secure MMS without network");
+ return new StatusPlusUri(MMS_REQUEST_AUTO_RETRY,
+ MessageData.RAW_TELEPHONY_STATUS_UNDEFINED,
+ messageUri,
+ SmsManager.MMS_ERROR_NO_DATA_NETWORK);
+ }
+ final Intent sentIntent = new Intent(SendStatusReceiver.MMS_SENT_ACTION,
+ messageUri,
+ context,
+ SendStatusReceiver.class);
+ extras.putInt(SendMessageAction.KEY_SUB_ID, 0);
+ extras.putBoolean(SendMessageAction.EXTRA_IS_SECURE, true);
+ sentIntent.putExtras(extras);
+ WhisperPushMessageSender sender = WhisperPushMessageSender.getInstance(context);
+
+ String text = message.getMessageText();
+ List<Pair<String, Uri>> attachment = new ArrayList<>();
+ for (MessagePartData data : message.getParts()) {
+ if (!data.isText()) {
+ attachment.add(new Pair<>(data.getContentType(), data.getContentUri()));
+ }
+ }
+
+ int result;
+ try {
+ sender.sendMultimediaMessage(recipients, attachment, text, message.getMessageId());
+ result = Activity.RESULT_OK;
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to send secure MMS", e);
+ result = SmsManager.MMS_ERROR_IO_ERROR;
+ } catch (Throwable t) {
+ Log.e(TAG, "Failed to send secure MMS", t);
+ result = SmsManager.MMS_ERROR_UNSPECIFIED;
+ }
+
+ final PendingIntent sentPendingIntent = PendingIntent.getBroadcast(
+ context,
+ 0 /*request code*/,
+ sentIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ try {
+ sentPendingIntent.send(context, result, null);
+ } catch (PendingIntent.CanceledException e) {
+ Log.e(TAG, "Sending pending intent canceled", e);
+ }
+
+ return STATUS_PENDING;
+
+ }
+
public static StatusPlusUri updateSentMmsMessageStatus(final Context context,
final Uri messageUri, final SendConf sendConf) {
int status = MMS_REQUEST_MANUAL_RETRY;
@@ -2571,6 +2637,31 @@ public class MmsUtils {
return status;
}
+ public static int sendSecureSmsMessage(final String recipient, final String messageText, final long timestamp) {
+ final Context context = Factory.get().getApplicationContext();
+ WhisperPush whisperPush = WhisperPush.getInstance(context);
+ if (!whisperPush.isSecureMessagingNetworkAvailable()) {
+ LogUtil.w(TAG, "MmsUtils: can't send secure SMS without network");
+ return MMS_REQUEST_AUTO_RETRY;
+ }
+ try {
+ // Send a single message
+ OutgoingMessage outgoingMessage = new OutgoingMessage.Builder()
+ .setDestination(recipient)
+ .setMessage(messageText)
+ .setTimestamp(timestamp)
+ .build();
+ boolean success = whisperPush.getMessageSender()
+ .sendTextMessage(outgoingMessage);
+ if (success) {
+ return MMS_REQUEST_SUCCEEDED;
+ }
+ } catch (final Exception e) {
+ LogUtil.e(TAG, "MmsUtils: failed to send secure SMS" + e, e);
+ }
+ return MMS_REQUEST_MANUAL_RETRY;
+ }
+
/**
* Delete SMS and MMS messages in a particular thread
*
@@ -2744,4 +2835,5 @@ public class MmsUtils {
}
return fileId;
}
+
}
diff --git a/src/com/android/messaging/ui/ConversationDrawables.java b/src/com/android/messaging/ui/ConversationDrawables.java
index cf858e2..00e112a 100644
--- a/src/com/android/messaging/ui/ConversationDrawables.java
+++ b/src/com/android/messaging/ui/ConversationDrawables.java
@@ -51,6 +51,7 @@ public class ConversationDrawables {
private int mIncomingErrorBubbleColor;
private int mIncomingAudioButtonColor;
private int mSelectedBubbleColor;
+ private int mSecuredBubbleCorol;
private int mThemeColor;
public static ConversationDrawables get() {
@@ -101,11 +102,12 @@ public class ConversationDrawables {
mIncomingAudioButtonColor =
resources.getColor(R.color.message_audio_button_color_incoming);
mSelectedBubbleColor = resources.getColor(R.color.message_bubble_color_selected);
+ mSecuredBubbleCorol = resources.getColor(R.color.message_bubble_color_secured);
mThemeColor = resources.getColor(R.color.primary_color);
}
public Drawable getBubbleDrawable(final boolean selected, final boolean incoming,
- final boolean needArrow, final boolean isError) {
+ final boolean secured, final boolean needArrow, final boolean isError) {
final Drawable protoDrawable;
if (needArrow) {
if (incoming) {
@@ -126,6 +128,8 @@ public class ConversationDrawables {
} else if (incoming) {
if (isError) {
color = mIncomingErrorBubbleColor;
+ } else if (secured) {
+ color = mSecuredBubbleCorol;
} else {
color = mThemeColor;
}
diff --git a/src/com/android/messaging/ui/conversation/ComposeMessageView.java b/src/com/android/messaging/ui/conversation/ComposeMessageView.java
index 17f8f74..c24f330 100644
--- a/src/com/android/messaging/ui/conversation/ComposeMessageView.java
+++ b/src/com/android/messaging/ui/conversation/ComposeMessageView.java
@@ -126,6 +126,8 @@ public class ComposeMessageView extends LinearLayout
private final Context mOriginalContext;
private int mSendWidgetMode = SEND_WIDGET_MODE_SELF_AVATAR;
+ private String mSecureText = " ";
+
// Shared data model object binding from the conversation.
private ImmutableBindingRef<ConversationData> mConversationDataModel;
@@ -384,8 +386,9 @@ public class ComposeMessageView extends LinearLayout
final String subject = mComposeSubjectText.getText().toString();
mBinding.getData().setMessageSubject(subject);
// Asynchronously check the draft against various requirements before sending.
- mBinding.getData().checkDraftForAction(checkMessageSize,
- mHost.getConversationSelfSubId(), new CheckDraftTaskCallback() {
+ mBinding.getData().checkDraftForAction(checkMessageSize, mHost.getConversationSelfSubId(),
+ ((ConversationFragment) mHost).getSecureMessagingHelper(),
+ new CheckDraftTaskCallback() {
@Override
public void onDraftChecked(DraftMessageData data, int result) {
mBinding.ensureBound(data);
@@ -434,6 +437,11 @@ public class ComposeMessageView extends LinearLayout
R.string.cant_send_message_without_active_subscription);
break;
+ case CheckDraftForSendTask.RESULT_PARTICIPANTS_MIXED:
+ MixedParticipantsSecurityWarning
+ .applySavedActionOrShowWarningDialog(getContext(), this, data);
+ break;
+
default:
break;
}
@@ -446,7 +454,6 @@ public class ComposeMessageView extends LinearLayout
public void run() {
sendMessageInternal(checkMessageSize);
}
-
});
}
}
@@ -705,12 +712,14 @@ public class ComposeMessageView extends LinearLayout
final SubscriptionListEntry subscriptionListEntry =
mConversationDataModel.getData().getSubscriptionEntryForSelfParticipant(
mBinding.getData().getSelfId(), false /* excludeDefault */);
+
if (subscriptionListEntry == null) {
- mComposeEditText.setHint(R.string.compose_message_view_hint_text);
+ mComposeEditText.setHint(getResources()
+ .getString(R.string.compose_message_view_hint_text, mSecureText));
} else {
- mComposeEditText.setHint(Html.fromHtml(getResources().getString(
- R.string.compose_message_view_hint_text_multi_sim,
- subscriptionListEntry.displayName)));
+ mComposeEditText.setHint(Html.fromHtml(getResources()
+ .getString(R.string.compose_message_view_hint_text_multi_sim, mSecureText,
+ subscriptionListEntry.displayName)));
}
} else {
int type = -1;
@@ -739,27 +748,27 @@ public class ComposeMessageView extends LinearLayout
switch (type) {
case ContentType.TYPE_IMAGE:
mComposeEditText.setHint(getResources().getQuantityString(
- R.plurals.compose_message_view_hint_text_photo, attachmentCount));
+ R.plurals.compose_message_view_hint_text_photo, attachmentCount, mSecureText));
break;
case ContentType.TYPE_AUDIO:
mComposeEditText.setHint(getResources().getQuantityString(
- R.plurals.compose_message_view_hint_text_audio, attachmentCount));
+ R.plurals.compose_message_view_hint_text_audio, attachmentCount, mSecureText));
break;
case ContentType.TYPE_VIDEO:
mComposeEditText.setHint(getResources().getQuantityString(
- R.plurals.compose_message_view_hint_text_video, attachmentCount));
+ R.plurals.compose_message_view_hint_text_video, attachmentCount, mSecureText));
break;
case ContentType.TYPE_VCARD:
mComposeEditText.setHint(getResources().getQuantityString(
- R.plurals.compose_message_view_hint_text_vcard, attachmentCount));
+ R.plurals.compose_message_view_hint_text_vcard, attachmentCount, mSecureText));
break;
case ContentType.TYPE_OTHER:
mComposeEditText.setHint(getResources().getQuantityString(
- R.plurals.compose_message_view_hint_text_attachments, attachmentCount));
+ R.plurals.compose_message_view_hint_text_attachments, attachmentCount, mSecureText));
break;
default:
@@ -769,6 +778,18 @@ public class ComposeMessageView extends LinearLayout
}
}
+ public void setMessageSecurity(boolean isSecured) {
+ if (isSecured) {
+ mSecureText = getResources().getString(R.string.secure_text);
+ } else {
+ mSecureText = " ";
+ }
+ if (mBinding.isBound()) {
+ mBinding.getData().setSecured(isSecured);
+ updateVisualsOnDraftChanged();
+ }
+ }
+
private void setSendButtonAccessibility(final int sendWidgetMode) {
switch (sendWidgetMode) {
case SEND_WIDGET_MODE_SELF_AVATAR:
diff --git a/src/com/android/messaging/ui/conversation/ConversationFragment.java b/src/com/android/messaging/ui/conversation/ConversationFragment.java
index a6a191a..49fe6b4 100644
--- a/src/com/android/messaging/ui/conversation/ConversationFragment.java
+++ b/src/com/android/messaging/ui/conversation/ConversationFragment.java
@@ -101,6 +101,7 @@ import com.android.messaging.util.LogUtil;
import com.android.messaging.util.OsUtil;
import com.android.messaging.util.PhoneUtils;
import com.android.messaging.util.SafeAsyncTask;
+import com.android.messaging.util.SecureMessagingHelper;
import com.android.messaging.util.TextUtil;
import com.android.messaging.util.UiUtils;
import com.android.messaging.util.UriUtil;
@@ -163,6 +164,7 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
private Parcelable mListState;
private ConversationFragmentHost mHost;
+ private SecureMessagingHelper mSecureMessagingHelper;
protected List<Integer> mFilterResults;
@@ -430,6 +432,28 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
}
}
);
+ mSecureMessagingHelper = new SecureMessagingHelper(getActivity(),
+ new SecureMessagingHelper.SecurityMessagingCallback() {
+ @Override
+ public void onParticipantsSecurityUpdated() {
+ if (isResumed()) {
+ setMessageSecurity(mSecureMessagingHelper.isAllParticipantsSecured());
+ }
+ }
+
+ @Override
+ public ConversationParticipantsData getParticipantsData() {
+ if (mBinding.isBound()) {
+ return mBinding.getData().getParticipants();
+ } else {
+ return null;
+ }
+ }
+ });
+ }
+
+ private void setMessageSecurity(boolean isAllParticipantsSecured) {
+ mComposeMessageView.setMessageSecurity(isAllParticipantsSecured);
}
/**
@@ -1190,6 +1214,8 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
public void onConversationParticipantDataLoaded(final ConversationData data) {
mBinding.ensureBound(data);
if (mBinding.getData().getParticipantsLoaded()) {
+ mSecureMessagingHelper.checkIfAllParticipantsSecuredAsync();
+
final boolean oneOnOne = mBinding.getData().getOtherParticipant() != null;
mAdapter.setOneOnOne(oneOnOne, true /* invalidate */);
@@ -1439,6 +1465,10 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
return self == null ? ParticipantData.DEFAULT_SELF_SUB_ID : self.getSubId();
}
+ public SecureMessagingHelper getSecureMessagingHelper() {
+ return mSecureMessagingHelper;
+ }
+
@Override
public void invalidateActionBar() {
mHost.invalidateActionBar();
diff --git a/src/com/android/messaging/ui/conversation/ConversationMessageView.java b/src/com/android/messaging/ui/conversation/ConversationMessageView.java
index 0bfb960..0123080 100644
--- a/src/com/android/messaging/ui/conversation/ConversationMessageView.java
+++ b/src/com/android/messaging/ui/conversation/ConversationMessageView.java
@@ -100,6 +100,7 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
private TextView mTitleTextView;
private TextView mMmsInfoTextView;
private LinearLayout mMessageTitleLayout;
+ private TextView mMessageSecuredTextView;
private TextView mSenderNameTextView;
private ContactIconView mContactIconView;
private ConversationMessageBubbleView mMessageBubble;
@@ -147,6 +148,7 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
mTitleTextView = (TextView) findViewById(R.id.message_title);
mMmsInfoTextView = (TextView) findViewById(R.id.mms_info);
mMessageTitleLayout = (LinearLayout) findViewById(R.id.message_title_layout);
+ mMessageSecuredTextView = (TextView) findViewById(R.id.message_secured);
mSenderNameTextView = (TextView) findViewById(R.id.message_sender_name);
mMessageBubble = (ConversationMessageBubbleView) findViewById(R.id.message_content);
mSubjectView = findViewById(R.id.subject_container);
@@ -311,6 +313,7 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
int titleResId = -1;
int statusResId = -1;
String statusText = null;
+ boolean isStatusSendComplete = false;
switch(mData.getStatus()) {
case MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING:
case MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING:
@@ -381,6 +384,7 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
default:
if (!mData.getCanClusterWithNextMessage()) {
statusText = mData.getFormattedReceivedTimeStamp();
+ isStatusSendComplete = true;
}
break;
}
@@ -410,6 +414,15 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
mData.getMmsSubject());
final boolean subjectVisible = !TextUtils.isEmpty(subjectText);
+ final boolean messageSecuredVisible = mData.getIsSecured() && isStatusSendComplete;
+ if (messageSecuredVisible) {
+ mMessageSecuredTextView.setText(getResources().getString(R.string.message_securely_sent));
+ mMessageSecuredTextView.setVisibility(View.VISIBLE);
+ } else {
+ mMessageSecuredTextView.setVisibility(View.GONE);
+
+ }
+
final boolean senderNameVisible = !mOneOnOne && !mData.getCanClusterWithNextMessage()
&& mData.getIsIncoming();
if (senderNameVisible) {
@@ -665,6 +678,7 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
final ConversationDrawables drawableProvider = ConversationDrawables.get();
final boolean incoming = mData.getIsIncoming();
final boolean outgoing = !incoming;
+ final boolean secured = mData.getIsSecured();
final boolean showArrow = shouldShowMessageBubbleArrow();
final int messageTopPaddingClustered =
@@ -699,6 +713,7 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
textBackground = drawableProvider.getBubbleDrawable(
isSelected(),
incoming,
+ secured,
false /* needArrow */,
mData.hasIncomingErrorStatus());
textMinHeight = messageTextMinHeightDefault;
@@ -726,6 +741,7 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
textBackground = drawableProvider.getBubbleDrawable(
isSelected(),
incoming,
+ secured,
shouldShowMessageBubbleArrow(),
mData.hasIncomingErrorStatus());
textMinHeight = messageTextMinHeightDefault;
@@ -982,6 +998,7 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
mSubjectLabel.setTextColor(getResources().getColor(subjectLabelColorResId));
mSenderNameTextView.setTextColor(getResources().getColor(timestampColorResId));
+ mMessageSecuredTextView.setTextColor(getResources().getColor(timestampColorResId));
}
/**
@@ -1112,8 +1129,8 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
final AudioAttachmentView audioView = (AudioAttachmentView) view;
audioView.bindMessagePartData(attachment, mData.getIsIncoming(), isSelected());
audioView.setBackground(ConversationDrawables.get().getBubbleDrawable(
- isSelected(), mData.getIsIncoming(), false /* needArrow */,
- mData.hasIncomingErrorStatus()));
+ isSelected(), mData.getIsIncoming(), mData.getIsSecured(),
+ false /* needArrow */, mData.hasIncomingErrorStatus()));
}
@Override
@@ -1129,8 +1146,8 @@ public class ConversationMessageView extends FrameLayout implements View.OnClick
personView.bind(DataModel.get().createVCardContactItemData(getContext(),
attachment));
personView.setBackground(ConversationDrawables.get().getBubbleDrawable(
- isSelected(), mData.getIsIncoming(), false /* needArrow */,
- mData.hasIncomingErrorStatus()));
+ isSelected(), mData.getIsIncoming(), mData.getIsSecured(),
+ false /* needArrow */, mData.hasIncomingErrorStatus()));
final int nameTextColorRes;
final int detailsTextColorRes;
if (isSelected()) {
diff --git a/src/com/android/messaging/ui/conversation/MixedParticipantsSecurityWarning.java b/src/com/android/messaging/ui/conversation/MixedParticipantsSecurityWarning.java
new file mode 100644
index 0000000..9fa8348
--- /dev/null
+++ b/src/com/android/messaging/ui/conversation/MixedParticipantsSecurityWarning.java
@@ -0,0 +1,76 @@
+package com.android.messaging.ui.conversation;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.view.View;
+import android.widget.Button;
+import android.widget.CheckBox;
+
+import com.android.messaging.R;
+import com.android.messaging.datamodel.data.DraftMessageData;
+
+/**
+ * If conversation includes secured and unsecured participants
+ * this class will inform user or apply saved action
+ */
+public class MixedParticipantsSecurityWarning {
+ public static final String MIXED_PARTICIPANTS_SEND_ACTION = "pref_key_mixed_participants_send_as_sms";
+
+ private MixedParticipantsSecurityWarning() {}
+
+ public static void applySavedActionOrShowWarningDialog(Context context,
+ DraftMessageData.CheckDraftTaskCallback callback, DraftMessageData data) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ boolean isSavedSendAsSms
+ = prefs.getBoolean(MIXED_PARTICIPANTS_SEND_ACTION, false);
+
+ if (isSavedSendAsSms) {
+ sendMessageAsSms(callback, data);
+ } else {
+ showDialogMixedParticipantsWarning(context, callback, data);
+ }
+ }
+
+ private static void sendMessageAsSms(DraftMessageData.CheckDraftTaskCallback callback, DraftMessageData data) {
+ callback.onDraftChecked(data, DraftMessageData.CheckDraftForSendTask.RESULT_PASSED);
+ }
+
+ private static void showDialogMixedParticipantsWarning(final Context context,
+ final DraftMessageData.CheckDraftTaskCallback callback, final DraftMessageData data) {
+ View warningView = View.inflate(context, R.layout.dialog_mixed_participants_warning, null);
+ final CheckBox dontShowAgain = (CheckBox) warningView.findViewById(R.id.dont_show_again);
+
+ final AlertDialog warning = new AlertDialog.Builder(context)
+ .setTitle(context.getResources().getString(R.string.mixed_participants_warning_dialog_title))
+ .setView(warningView)
+ .setPositiveButton(R.string.mixed_participants_send_as_sms_action,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (dontShowAgain.isChecked()) {
+ saveActionMixedParticipantsSendAsSms(context, true);
+ }
+ sendMessageAsSms(callback, data);
+ }
+ })
+ .setNegativeButton(R.string.mixed_participants_cancel_action, null)
+ .create();
+
+ warning.show();
+
+ Button sendAsSms = warning.getButton(DialogInterface.BUTTON_POSITIVE);
+ sendAsSms.setTextColor(context.getResources().getColor(R.color.button_bar_action_button_text_color));
+ Button cancel = warning.getButton(DialogInterface.BUTTON_NEGATIVE);
+ cancel.setTextColor(context.getResources().getColor(R.color.dialog_btn_grey));
+ }
+
+ private static void saveActionMixedParticipantsSendAsSms(Context context, boolean sendAsSms) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ prefs.edit()
+ .putBoolean(MIXED_PARTICIPANTS_SEND_ACTION, sendAsSms)
+ .apply();
+ }
+}
diff --git a/src/com/android/messaging/util/SecureMessagingHelper.java b/src/com/android/messaging/util/SecureMessagingHelper.java
new file mode 100644
index 0000000..8dfd276
--- /dev/null
+++ b/src/com/android/messaging/util/SecureMessagingHelper.java
@@ -0,0 +1,103 @@
+package com.android.messaging.util;
+
+import android.content.Context;
+import android.os.AsyncTask;
+
+import com.android.messaging.datamodel.data.ConversationParticipantsData;
+import com.android.messaging.datamodel.data.ParticipantData;
+
+import org.whispersystems.whisperpush.WhisperPush;
+import org.whispersystems.whisperpush.directory.Directory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.whispersystems.whisperpush.directory.Directory.STATE_ALL_CONTACTS_SECURE;
+import static org.whispersystems.whisperpush.directory.Directory.STATE_ALL_CONTACTS_UNSECURE;
+import static org.whispersystems.whisperpush.directory.Directory.STATE_CONTACTS_MIXED;
+
+/**
+ * Provides API for secure messaging (currently - via WhisperPush)
+ */
+public class SecureMessagingHelper {
+ private static final int STATE_UNKNOWN = 0;
+
+ private Context mContext;
+ private WhisperPush mWhisperPush;
+ private SecurityMessagingCallback mCallback;
+ private int mParticipantsSecurityState = STATE_UNKNOWN;
+ private boolean mIsUpdatingParticipantsSecurity;
+
+ public interface SecurityMessagingCallback {
+ void onParticipantsSecurityUpdated();
+ ConversationParticipantsData getParticipantsData();
+ }
+
+ public SecureMessagingHelper(Context context, SecurityMessagingCallback callback) {
+ mContext = context;
+ mWhisperPush = WhisperPush.getInstance(context);
+ mCallback = callback;
+ }
+
+ public void checkIfAllParticipantsSecuredAsync() {
+ mIsUpdatingParticipantsSecurity = true;
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ checkIfAllParticipantsSecured();
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ super.onPostExecute(result);
+ mCallback.onParticipantsSecurityUpdated();
+ mIsUpdatingParticipantsSecurity = false;
+ }
+ }.execute();
+
+ }
+
+ public void checkIfAllParticipantsSecured() {
+ if (mWhisperPush.isSecureMessagingActive()) {
+ List<String> participantsSendDestinations = getSendDestinations();
+
+ if (participantsSendDestinations != null) {
+ mParticipantsSecurityState
+ = Directory.getInstance(mContext).isAllActiveNumbers(participantsSendDestinations);
+ } else {
+ mParticipantsSecurityState = STATE_UNKNOWN;
+ }
+ } else {
+ mParticipantsSecurityState = STATE_UNKNOWN;
+ }
+ }
+
+ private List<String> getSendDestinations() {
+ ConversationParticipantsData participantsData = mCallback.getParticipantsData();
+
+ if (participantsData != null) {
+ List<String> participantsSendDestinations = new ArrayList<String>();
+
+ for (ParticipantData participant : participantsData.getParticipantListExcludingSelf()){
+ participantsSendDestinations.add(participant.getSendDestination());
+ }
+
+ return participantsSendDestinations;
+ } else {
+ return null;
+ }
+ }
+
+ public boolean isUpdatingParticipantsSecurity() {
+ return mIsUpdatingParticipantsSecurity;
+ }
+
+ public boolean isAllParticipantsSecured() {
+ return mParticipantsSecurityState == STATE_ALL_CONTACTS_SECURE;
+ }
+
+ public boolean isParticipantsMixed() {
+ return mParticipantsSecurityState == STATE_CONTACTS_MIXED;
+ }
+}
diff --git a/tests/src/com/android/messaging/ui/conversation/ComposeMessageViewTest.java b/tests/src/com/android/messaging/ui/conversation/ComposeMessageViewTest.java
index 2dd2a89..50c723f 100644
--- a/tests/src/com/android/messaging/ui/conversation/ComposeMessageViewTest.java
+++ b/tests/src/com/android/messaging/ui/conversation/ComposeMessageViewTest.java
@@ -38,6 +38,7 @@ import com.android.messaging.ui.conversation.ComposeMessageView.IComposeMessageV
import com.android.messaging.util.BugleGservices;
import com.android.messaging.util.FakeMediaUtil;
import com.android.messaging.util.ImeUtil;
+import com.android.messaging.util.SecureMessagingHelper;
import org.mockito.ArgumentMatcher;
import org.mockito.Matchers;
@@ -117,6 +118,7 @@ public class ComposeMessageViewTest extends ViewTest<ComposeMessageView> {
return null;
}
}).when(mockDraftMessageData).checkDraftForAction(Mockito.anyBoolean(), Mockito.anyInt(),
+ Mockito.<SecureMessagingHelper>any(),
Mockito.<CheckDraftTaskCallback>any(),
Mockito.<Binding<DraftMessageData>>any());