summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-x[-rw-r--r--]AndroidManifest.xml4
-rwxr-xr-xres/drawable/ic_library_music_black_24px.xml15
-rwxr-xr-xres/drawable/ic_library_music_white_24px.xml15
-rwxr-xr-xres/drawable/ic_play_circle_filled_white_24px.xml14
-rw-r--r--res/layout/audio_list_item_view.xml56
-rw-r--r--res/layout/conversation_list_item_view.xml2
-rw-r--r--res/layout/gallery_grid_item_view.xml16
-rw-r--r--res/layout/mediapicker_audio_list_chooser.xml39
-rw-r--r--res/layout/widget_conversation.xml2
-rw-r--r--res/mipmap-hdpi/ic_launcher.png (renamed from res/drawable-hdpi/ic_launcher.png)bin2211 -> 2211 bytes
-rw-r--r--res/mipmap-mdpi/ic_launcher.png (renamed from res/drawable-mdpi/ic_launcher.png)bin1433 -> 1433 bytes
-rw-r--r--res/mipmap-xhdpi/ic_launcher.png (renamed from res/drawable-xhdpi/ic_launcher.png)bin2922 -> 2922 bytes
-rw-r--r--res/mipmap-xxhdpi/ic_launcher.png (renamed from res/drawable-xxhdpi/ic_launcher.png)bin4610 -> 4610 bytes
-rw-r--r--res/mipmap-xxxhdpi/ic_launcher.png (renamed from res/drawable-xxxhdpi/ic_launcher.png)bin6200 -> 6200 bytes
-rw-r--r--res/values-ldrtl/styles.xml2
-rwxr-xr-xres/values/cm_colors.xml19
-rw-r--r--res/values/cm_constants.xml29
-rw-r--r--res/values/cm_strings.xml38
-rw-r--r--res/values/colors.xml16
-rw-r--r--res/values/dimens.xml7
-rw-r--r--res/values/strings.xml5
-rwxr-xr-x[-rw-r--r--]res/values/styles.xml16
-rw-r--r--res/xml-v21/preferences_application.xml14
-rw-r--r--res/xml-v23/preferences_application.xml14
-rw-r--r--res/xml/preferences_application.xml14
-rw-r--r--src/com/android/messaging/datamodel/AudioBoundCursorLoader.java49
-rw-r--r--src/com/android/messaging/datamodel/BugleNotifications.java9
-rw-r--r--src/com/android/messaging/datamodel/DataModel.java3
-rw-r--r--src/com/android/messaging/datamodel/DataModelImpl.java6
-rw-r--r--src/com/android/messaging/datamodel/DatabaseHelper.java4
-rw-r--r--src/com/android/messaging/datamodel/GalleryBoundCursorLoader.java3
-rw-r--r--src/com/android/messaging/datamodel/action/BugleActionToasts.java1
-rw-r--r--src/com/android/messaging/datamodel/action/ProcessPendingMessagesAction.java170
-rw-r--r--src/com/android/messaging/datamodel/data/AudioListItemData.java90
-rw-r--r--src/com/android/messaging/datamodel/data/ConversationListItemData.java7
-rw-r--r--src/com/android/messaging/datamodel/data/GalleryGridItemData.java67
-rw-r--r--src/com/android/messaging/datamodel/data/MediaPickerData.java32
-rw-r--r--src/com/android/messaging/datamodel/data/MessagePartData.java29
-rw-r--r--src/com/android/messaging/datamodel/data/PeopleOptionsItemData.java7
-rw-r--r--src/com/android/messaging/datamodel/media/AvatarRequest.java2
-rw-r--r--src/com/android/messaging/ui/ClassZeroActivity.java3
-rw-r--r--src/com/android/messaging/ui/appsettings/ApplicationSettingsActivity.java7
-rw-r--r--src/com/android/messaging/ui/conversation/ComposeMessageView.java11
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationFragment.java4
-rw-r--r--src/com/android/messaging/ui/conversationlist/ConversationListItemView.java25
-rw-r--r--src/com/android/messaging/ui/conversationlist/ConversationListSwipeHelper.java8
-rw-r--r--src/com/android/messaging/ui/mediapicker/AudioListAdapter.java61
-rw-r--r--src/com/android/messaging/ui/mediapicker/AudioListChooser.java219
-rw-r--r--src/com/android/messaging/ui/mediapicker/AudioListItemView.java111
-rw-r--r--src/com/android/messaging/ui/mediapicker/AudioListView.java311
-rw-r--r--src/com/android/messaging/ui/mediapicker/AudioMediaChooser.java4
-rw-r--r--src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java99
-rw-r--r--src/com/android/messaging/ui/mediapicker/GalleryGridView.java20
-rw-r--r--src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java10
-rw-r--r--src/com/android/messaging/ui/mediapicker/MediaPicker.java2
-rw-r--r--src/com/android/messaging/ui/mediapicker/MediaPickerListView.java45
-rw-r--r--src/com/android/messaging/util/NotificationUtil.java57
-rw-r--r--src/com/android/messaging/util/UiUtils.java10
-rw-r--r--src/com/cyanogenmod/messaging/util/PrefsUtils.java52
-rw-r--r--tests/src/com/android/messaging/datamodel/FakeDataModel.java6
60 files changed, 1779 insertions, 102 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 5ab5910..b58533c 100644..100755
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -58,7 +58,7 @@
<application
android:name="com.android.messaging.BugleApplication"
android:allowBackup="false"
- android:icon="@drawable/ic_launcher"
+ android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/BugleTheme"
android:supportsRtl="true">
@@ -424,7 +424,7 @@
</activity>
<activity android:name=".ui.SmsStorageLowWarningActivity"
- android:theme="@style/Invisible"
+ android:theme="@style/InvisibleNoDisplay"
android:configChanges="orientation|screenSize|keyboardHidden" />
<activity android:name=".ui.appsettings.ApnSettingsActivity"
diff --git a/res/drawable/ic_library_music_black_24px.xml b/res/drawable/ic_library_music_black_24px.xml
new file mode 100755
index 0000000..761d839
--- /dev/null
+++ b/res/drawable/ic_library_music_black_24px.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:pathData="M0 0h24v24H0z" />
+ <path
+ android:fillColor="#000000"
+ android:pathData="M20 2H8c-1.1 0-2 .9-2 2v12c0 1.1 .9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-2
+5h-3v5.5c0 1.38-1.12 2.5-2.5 2.5S10 13.88 10 12.5s1.12-2.5 2.5-2.5c.57 0 1.08
+.19 1.5 .51 V5h4v2zM4 6H2v14c0 1.1 .9 2 2 2h14v-2H4V6z" />
+</vector> \ No newline at end of file
diff --git a/res/drawable/ic_library_music_white_24px.xml b/res/drawable/ic_library_music_white_24px.xml
new file mode 100755
index 0000000..28ac927
--- /dev/null
+++ b/res/drawable/ic_library_music_white_24px.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:pathData="M0 0h24v24H0z" />
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M20 2H8c-1.1 0-2 .9-2 2v12c0 1.1 .9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-2
+5h-3v5.5c0 1.38-1.12 2.5-2.5 2.5S10 13.88 10 12.5s1.12-2.5 2.5-2.5c.57 0 1.08
+.19 1.5 .51 V5h4v2zM4 6H2v14c0 1.1 .9 2 2 2h14v-2H4V6z" />
+</vector> \ No newline at end of file
diff --git a/res/drawable/ic_play_circle_filled_white_24px.xml b/res/drawable/ic_play_circle_filled_white_24px.xml
new file mode 100755
index 0000000..2f3b1b9
--- /dev/null
+++ b/res/drawable/ic_play_circle_filled_white_24px.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:pathData="M0 0h24v24H0z" />
+ <path
+ android:fillColor="#FFFFFF"
+ android:pathData="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 14.5v-9l6
+4.5-6 4.5z" />
+</vector> \ No newline at end of file
diff --git a/res/layout/audio_list_item_view.xml b/res/layout/audio_list_item_view.xml
new file mode 100644
index 0000000..e22acc1
--- /dev/null
+++ b/res/layout/audio_list_item_view.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<com.android.messaging.ui.mediapicker.AudioListItemView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/audio_list_item_height"
+ android:background="@color/gallery_image_default_background"
+ android:clickable="true">
+
+ <FrameLayout
+ android:layout_gravity="center_vertical"
+ android:layout_marginLeft="@dimen/audio_list_item_icon_margin"
+ android:layout_width="@dimen/audio_list_item_icon_size"
+ android:layout_height="@dimen/audio_list_item_icon_size">
+
+ <CheckBox
+ android:id="@+id/audio_checkbox"
+ style="@style/GalleryGridItemViewCheckBoxStyle"
+ android:button="@drawable/gallery_checkbox_selector"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
+ <ImageView
+ android:id="@+id/audio_button"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/transparent_button_background"
+ android:scaleType="fitCenter"
+ android:src="@drawable/ic_library_music_black_24px"
+ android:contentDescription="@string/video_thumbnail_view_play_button_content_description"/>
+
+ </FrameLayout>
+
+ <TextView
+ android:id="@+id/audio_filename"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/audio_list_item_text_height"
+ android:layout_marginLeft="@dimen/audio_list_item_text_margin"
+ android:layout_gravity="center_vertical"
+ android:textColor="@color/button_text"/>
+
+</com.android.messaging.ui.mediapicker.AudioListItemView>
diff --git a/res/layout/conversation_list_item_view.xml b/res/layout/conversation_list_item_view.xml
index 636616b..43cb3ee 100644
--- a/res/layout/conversation_list_item_view.xml
+++ b/res/layout/conversation_list_item_view.xml
@@ -37,7 +37,6 @@
android:layout_height="wrap_content"
android:gravity="center_vertical|left"
android:visibility="gone"
- android:src="@drawable/ic_archive_small_dark"
android:importantForAccessibility="no"
android:contentDescription="@null"/>
<FrameLayout
@@ -50,7 +49,6 @@
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|right"
android:visibility="gone"
- android:src="@drawable/ic_archive_small_dark"
android:importantForAccessibility="no"
android:contentDescription="@null"/>
</LinearLayout>
diff --git a/res/layout/gallery_grid_item_view.xml b/res/layout/gallery_grid_item_view.xml
index 8b7ee58..c5e32df 100644
--- a/res/layout/gallery_grid_item_view.xml
+++ b/res/layout/gallery_grid_item_view.xml
@@ -27,6 +27,22 @@
android:layout_height="match_parent"
android:scaleType="centerCrop" />
+ <FrameLayout
+ android:id="@+id/video_button"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:visibility="invisible">
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:background="@drawable/transparent_button_background"
+ android:scaleType="fitCenter"
+ android:src="@drawable/ic_play_circle_filled_white_24px"
+ android:contentDescription="@string/video_thumbnail_view_play_button_content_description"/>
+ </FrameLayout>
+
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/res/layout/mediapicker_audio_list_chooser.xml b/res/layout/mediapicker_audio_list_chooser.xml
new file mode 100644
index 0000000..fbab0bc
--- /dev/null
+++ b/res/layout/mediapicker_audio_list_chooser.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <com.android.messaging.ui.mediapicker.AudioListView
+ android:id="@+id/audio_list_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="6dp"
+ android:background="@android:color/white" />
+
+ <!-- This view will hide all other views if the required permission is not granted -->
+ <TextView
+ android:id="@+id/missing_permission_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:text="@string/enable_permission_procedure"
+ android:contentDescription="@string/enable_permission_procedure_description"
+ android:background="@android:color/white"
+ android:gravity="center"
+ android:visibility="gone" />
+</FrameLayout> \ No newline at end of file
diff --git a/res/layout/widget_conversation.xml b/res/layout/widget_conversation.xml
index a9cda98..eaf1dcf 100644
--- a/res/layout/widget_conversation.xml
+++ b/res/layout/widget_conversation.xml
@@ -39,7 +39,7 @@
android:freezesText="true" />
<ImageView
android:id="@+id/launcher_icon"
- android:src="@drawable/ic_launcher"
+ android:src="@mipmap/ic_launcher"
android:layout_height="wrap_content"
android:layout_width="0dip"
android:layout_weight = "1"
diff --git a/res/drawable-hdpi/ic_launcher.png b/res/mipmap-hdpi/ic_launcher.png
index 5e4e62f..5e4e62f 100644
--- a/res/drawable-hdpi/ic_launcher.png
+++ b/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher.png b/res/mipmap-mdpi/ic_launcher.png
index ef6a3c5..ef6a3c5 100644
--- a/res/drawable-mdpi/ic_launcher.png
+++ b/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher.png b/res/mipmap-xhdpi/ic_launcher.png
index ad1acb4..ad1acb4 100644
--- a/res/drawable-xhdpi/ic_launcher.png
+++ b/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_launcher.png b/res/mipmap-xxhdpi/ic_launcher.png
index de56e98..de56e98 100644
--- a/res/drawable-xxhdpi/ic_launcher.png
+++ b/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_launcher.png b/res/mipmap-xxxhdpi/ic_launcher.png
index 9ac88b4..9ac88b4 100644
--- a/res/drawable-xxxhdpi/ic_launcher.png
+++ b/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/res/values-ldrtl/styles.xml b/res/values-ldrtl/styles.xml
index bd270c7..abfd16c 100644
--- a/res/values-ldrtl/styles.xml
+++ b/res/values-ldrtl/styles.xml
@@ -38,7 +38,7 @@
<item name="android:background">@null</item>
<item name="android:scrollHorizontally">false</item>
<item name="android:textCursorDrawable">@null</item>
- <item name="android:inputType">textShortMessage|textAutoCorrect|textCapSentences|textMultiLine</item>
+ <item name="android:inputType">textAutoCorrect|textCapSentences|textMultiLine</item>
</style>
<style name="ConversationComposeSubjectText" parent="ConversationComposeSendText">
diff --git a/res/values/cm_colors.xml b/res/values/cm_colors.xml
new file mode 100755
index 0000000..05e91e0
--- /dev/null
+++ b/res/values/cm_colors.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The CyanogenMod Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+--><resources>
+ <color name="cm_accent">#FF9800</color>
+ <color name="button_text">#000000</color>
+</resources>
diff --git a/res/values/cm_constants.xml b/res/values/cm_constants.xml
new file mode 100644
index 0000000..5dcac5d
--- /dev/null
+++ b/res/values/cm_constants.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The CyanogenMod Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+
+ <!-- Preference keys for user-visible settings -->
+ <!-- Application-wide settings -->
+ <string name="swipe_right_deletes_conversation_key" translatable="false">swipe_right_deletes_conversation</string>
+
+ <!-- This should really go into a config xml, but whoever wrote this app is an idiot, so follow their pattern -->
+ <bool name="swipe_right_deletes_conversation_default" translatable="false">false</bool>
+
+ <!-- Preference keys for user-visible settings -->
+ <!-- Application-wide settings -->
+ <bool name="show_emoticons_pref_default" translatable="false">true</bool>
+</resources>
diff --git a/res/values/cm_strings.xml b/res/values/cm_strings.xml
new file mode 100644
index 0000000..1914da1
--- /dev/null
+++ b/res/values/cm_strings.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Swipe to delete conversation -->
+ <string name="swipe_to_delete_conversation_pref_title">Swipe to delete</string>
+ <string name="swipe_to_delete_conversation_pref_summary">Swipe to the right to delete a conversation</string>
+
+ <!-- Show emoticons -->
+ <string name="show_emoticons_pref_title">Emoticons access</string>
+ <string name="show_emoticons_pref_summary">Show the emoticons key on the keyboard</string>
+
+ <!-- Audio Library Tab for MMS attachments -->
+ <string name="mediapicker_galleryChooserDescription_cm">Choose images or videos from this
+ device</string>
+ <string name="mediapicker_audioListChooserDescription">Choose audio files from this
+ device</string>
+ <string name="mediapicker_gallery_image_item_attachment_too_large">Can\'t attach video.
+ Max message size exceeded.</string>
+ <string name="mediapicker_audio_list_title">Choose audio</string>
+ <string name="mediapicker_audio_list_item_selected_content_description">Selected audio</string>
+ <string name="mediapicker_audio_list_item_unselected_content_description">Tap to select</string>
+ <string name="mediapicker_audio_list_title_selection"><xliff:g id="count">%d</xliff:g> selected
+ </string>
+</resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index f33e105..b425a51 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -62,13 +62,13 @@
<color name="message_text_color_outgoing">#ff323232</color>
<color name="conversation_background">#eeeeee</color>
<color name="conversation_edge_effect">#9d9d9d</color>
- <color name="compose_message_send_color">@color/primary_color</color>
+ <color name="compose_message_send_color">@color/cm_accent</color>
<color name="compose_message_send_color_pressed">#999999</color>
<color name="message_bubble_color_outgoing">#ffffffff</color>
<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_image_selected_tint">#80689F38</color>
+ <color name="message_bubble_color_selected">@color/cm_accent</color>
+ <color name="message_image_selected_tint">#80FF9800</color>
<color name="generic_video_icon">#ff808080</color>
<!-- Base color used for color filtering. -->
@@ -87,7 +87,7 @@
<color name="compose_contact_divider">#44000000</color>
<color name="contact_picker_tab_pressed">#ddffffff</color>
<color name="contact_picker_tab_underline">@android:color/white</color>
- <color name="contact_list_alphabet_header">@color/primary_color</color>
+ <color name="contact_list_alphabet_header">@color/cm_accent</color>
<color name="contact_picker_background">#ffffff</color>
<color name="chips_dropdown_background_activated">#4285f4</color>
<color name="chips_dropdown_background_pressed">#ededed</color>
@@ -109,7 +109,7 @@
<color name="audio_picker_hint_text_color">#40000000</color>
<color name="audio_picker_timer_text_color">#323232</color>
<color name="audio_attachment_timer_text_color">#323232</color>
- <color name="audio_progress_bar_color">@color/primary_color</color>
+ <color name="audio_progress_bar_color">@color/cm_accent</color>
<color name="notification_sender_text">#9A9A9A</color>
<color name="notification_secondary_text">#FFFFFF</color>
@@ -124,15 +124,15 @@
<color name="people_and_options_header_text">#6d6d6d</color>
<color name="people_and_options_list_divider">#cccccc</color>
- <color name="fab_color">@color/primary_color</color>
+ <color name="fab_color">@color/cm_accent</color>
<color name="fab_pressed_color">#3ea4dc</color>
<color name="fab_ripple">#40ffffff</color>
<color name="message_text_counter_color">#555555</color>
- <color name="mms_indicator_color">#8BC34A</color>
+ <color name="mms_indicator_color">#681faf</color>
<color name="list_empty_text">#6d6d6d</color>
<color name="low_storage_action_item_color">#ff000000</color>
- <color name="unblock_item_text_color">@color/primary_color</color>
+ <color name="unblock_item_text_color">@color/cm_accent</color>
<color name="open_conversation_animation_background_shadow">#40000000</color>
<color name="compose_notification_bar_background">@color/primary_color</color>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 5ff0eb7..6f82d72 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -92,6 +92,13 @@
<dimen name="multiple_attachment_preview_padding">1dp</dimen>
<dimen name="attachment_preview_more_items_text_size">22sp</dimen>
+ <dimen name="audio_list_item_height">48dp</dimen>
+ <dimen name="audio_list_item_icon_size">20dp</dimen>
+ <dimen name="audio_list_item_icon_margin">26dp</dimen>
+ <dimen name="audio_list_item_text_margin">26dp</dimen>
+ <dimen name="audio_list_item_text_height">24dp</dimen>
+
+
<item name="letter_to_tile_ratio" type="dimen">67%</item>
<item name="sim_identifier_to_tile_ratio" type="dimen">28%</item>
<item name="small_sim_identifier_to_tile_ratio" type="dimen">75%</item>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 012d87f..3d21f3a 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -51,7 +51,8 @@
<string name="contact_list_send_to_text">Send to <xliff:g id="destination">%s</xliff:g></string>
<string name="mediapicker_cameraChooserDescription">Capture pictures or video</string>
- <string name="mediapicker_galleryChooserDescription">Choose images from this device</string>
+ <string name="mediapicker_galleryChooserDescription">Choose images or videos from this
+ device</string>
<string name="mediapicker_audioChooserDescription">Record audio</string>
<string name="mediapicker_gallery_title">Choose photo</string>
<string name="mediapicker_gallery_item_selected_content_description">The media is selected.</string>
@@ -61,9 +62,7 @@
<string name="mediapicker_gallery_image_item_description">image <xliff:g id="date">%1$tB %1$te %1$tY %1$tl %1$tM %1$tp</xliff:g></string>
<string name="mediapicker_gallery_image_item_description_no_date">image</string>
<string name="mediapicker_audio_title">Record audio</string>
-
<string name="action_share">Share</string>
-
<string name="posted_just_now">"Just now"</string>
<string name="posted_now">"Now"</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 582c755..5f473ad 100644..100755
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -34,7 +34,7 @@
<item name="android:dropDownListViewStyle">@style/DropDownListViewStyle</item>
<item name="colorPrimary">@color/action_bar_background_color</item>
<item name="colorPrimaryDark">@color/action_bar_background_color_dark</item>
- <item name="colorAccent">@color/action_bar_background_color</item>
+ <item name="colorAccent">@color/cm_accent</item>
<item name="android:textColorHighlight">@color/text_highlight_color</item>
<item name="actionBarStyle">@style/BugleActionBar</item>
<item name="apnPreferenceStyle">@style/ApnPreference</item>
@@ -47,6 +47,7 @@
<style name="BugleTheme.ConversationActivityBase" parent="BugleTheme">
<item name="android:windowBackground">@color/conversation_background</item>
<item name="windowActionBarOverlay">true</item>
+ <item name="colorAccent">@color/cm_accent</item>
<item name="android:fastScrollPreviewBackgroundLeft">@drawable/contacts_fastscroll_label_left</item>
<item name="android:fastScrollPreviewBackgroundRight">@drawable/contacts_fastscroll_label_right</item>
</style>
@@ -76,6 +77,14 @@
<item name="android:windowNoDisplay">true</item>
</style>
+ <style name="InvisibleNoDisplay" parent="BugleBaseTheme">
+ <item name="android:windowBackground">@null</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowAnimationStyle">@null</item>
+ <item name="android:windowDisablePreview">true</item>
+ </style>
+
<style name="BugleActionBar" parent="@style/Widget.AppCompat.Light.ActionBar.Solid">
<item name="height">@dimen/action_bar_height</item>
<item name="displayOptions">showTitle</item>
@@ -133,7 +142,8 @@
<item name="android:background">@null</item>
<item name="android:scrollHorizontally">false</item>
<item name="android:textCursorDrawable">@null</item>
- <item name="android:inputType">textShortMessage|textAutoCorrect|textCapSentences|textMultiLine</item>
+ <item name="android:inputType">textAutoCorrect|textCapSentences|textMultiLine</item>
+ <item name="android:colorAccent">@color/cm_accent</item>
</style>
<style name="ConversationComposeSubjectText" parent="ConversationComposeSendText">
@@ -352,7 +362,7 @@
<item name="android:background">@null</item>
</style>
- <style name="DropDownListViewStyle">
+ <style name="DropDownListViewStyle" parent="Widget.AppCompat.ListView.DropDown">
<item name="android:dividerHeight">0dp</item>
</style>
diff --git a/res/xml-v21/preferences_application.xml b/res/xml-v21/preferences_application.xml
index 5d8ee4c..c403c06 100644
--- a/res/xml-v21/preferences_application.xml
+++ b/res/xml-v21/preferences_application.xml
@@ -66,6 +66,20 @@
android:persistent="true"
android:dependency="@string/notifications_enabled_pref_key" />
+ <SwitchPreference
+ android:key="pref_show_emoticons"
+ android:title="@string/show_emoticons_pref_title"
+ android:persistent="true"
+ android:defaultValue="@bool/show_emoticons_pref_default"
+ android:summary="@string/show_emoticons_pref_summary" />
+
+ <SwitchPreference
+ android:key="@string/swipe_right_deletes_conversation_key"
+ android:title="@string/swipe_to_delete_conversation_pref_title"
+ android:summary="@string/swipe_to_delete_conversation_pref_summary"
+ android:defaultValue="false"
+ android:persistent="true" />
+
<PreferenceScreen
android:key="@string/advanced_pref_key"
android:title="@string/advanced_settings" />
diff --git a/res/xml-v23/preferences_application.xml b/res/xml-v23/preferences_application.xml
index 8fbadc4..d9cc151 100644
--- a/res/xml-v23/preferences_application.xml
+++ b/res/xml-v23/preferences_application.xml
@@ -67,6 +67,20 @@
android:persistent="true"
android:dependency="@string/notifications_enabled_pref_key" />
+ <SwitchPreference
+ android:key="pref_show_emoticons"
+ android:title="@string/show_emoticons_pref_title"
+ android:persistent="true"
+ android:defaultValue="@bool/show_emoticons_pref_default"
+ android:summary="@string/show_emoticons_pref_summary" />
+
+ <SwitchPreference
+ android:key="@string/swipe_right_deletes_conversation_key"
+ android:title="@string/swipe_to_delete_conversation_pref_title"
+ android:summary="@string/swipe_to_delete_conversation_pref_summary"
+ android:defaultValue="false"
+ android:persistent="true" />
+
<PreferenceScreen
android:key="@string/advanced_pref_key"
android:title="@string/advanced_settings" />
diff --git a/res/xml/preferences_application.xml b/res/xml/preferences_application.xml
index 7a18d09..03b868b 100644
--- a/res/xml/preferences_application.xml
+++ b/res/xml/preferences_application.xml
@@ -66,6 +66,20 @@
android:persistent="true"
android:dependency="@string/notifications_enabled_pref_key" />
+ <CheckBoxPreference
+ android:key="pref_show_emoticons"
+ android:title="@string/show_emoticons_pref_title"
+ android:persistent="true"
+ android:defaultValue="@bool/show_emoticons_pref_default"
+ android:summary="@string/show_emoticons_pref_summary" />
+
+ <SwitchPreference
+ android:key="@string/swipe_right_deletes_conversation_key"
+ android:title="@string/swipe_to_delete_conversation_pref_title"
+ android:summary="@string/swipe_to_delete_conversation_pref_summary"
+ android:defaultValue="false"
+ android:persistent="true" />
+
<PreferenceScreen
android:key="@string/advanced_pref_key"
android:title="@string/advanced_settings" />
diff --git a/src/com/android/messaging/datamodel/AudioBoundCursorLoader.java b/src/com/android/messaging/datamodel/AudioBoundCursorLoader.java
new file mode 100644
index 0000000..406432b
--- /dev/null
+++ b/src/com/android/messaging/datamodel/AudioBoundCursorLoader.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.messaging.datamodel;
+
+import android.content.Context;
+import android.net.Uri;
+import android.provider.MediaStore.Files;
+import android.provider.MediaStore.Files.FileColumns;
+import android.provider.MediaStore.Images.Media;
+import com.android.messaging.datamodel.data.AudioListItemData;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.google.common.base.Joiner;
+
+/**
+ * A BoundCursorLoader that reads local media on the device.
+ */
+public class AudioBoundCursorLoader extends BoundCursorLoader {
+ public static final String MEDIA_SCANNER_VOLUME_EXTERNAL = "external";
+ private static final Uri STORAGE_URI = Files.getContentUri(MEDIA_SCANNER_VOLUME_EXTERNAL);
+ private static final String SORT_ORDER = Media.DATE_MODIFIED + " DESC";
+ private static final String TAG = AudioBoundCursorLoader.class.getSimpleName();
+ private static final String AUDIO_SELECTION = createSelection(
+ MessagePartData.ACCEPTABLE_AUDIO_TYPES,
+ new Integer[] {FileColumns.MEDIA_TYPE_AUDIO});
+
+ public AudioBoundCursorLoader(final String bindingId, final Context context) {
+ super(bindingId, context, STORAGE_URI, AudioListItemData.AUDIO_PROJECTION,
+ AUDIO_SELECTION, null, SORT_ORDER);
+ }
+
+ private static String createSelection(final String[] mimeTypes, Integer[] mediaTypes) {
+ return Media.MIME_TYPE + " IN ('" + Joiner.on("','").join(mimeTypes) + "') AND "
+ + FileColumns.MEDIA_TYPE + " IN (" + Joiner.on(',').join(mediaTypes) + ")";
+ }
+}
diff --git a/src/com/android/messaging/datamodel/BugleNotifications.java b/src/com/android/messaging/datamodel/BugleNotifications.java
index b796e73..ffe9105 100644
--- a/src/com/android/messaging/datamodel/BugleNotifications.java
+++ b/src/com/android/messaging/datamodel/BugleNotifications.java
@@ -191,6 +191,15 @@ public class BugleNotifications {
}
/**
+ * Play a sound to notify arrival of a class 0 message
+ *
+ */
+ public static void playClassZeroNotification() {
+ final Uri ringtoneUri = RingtoneUtil.getNotificationRingtoneUri(null);
+ playObservableConversationNotificationSound(ringtoneUri);
+ }
+
+ /**
* Cancel all notifications of a certain type.
*
* @param type Message or error notifications from Constants.
diff --git a/src/com/android/messaging/datamodel/DataModel.java b/src/com/android/messaging/datamodel/DataModel.java
index 936b51c..b53a217 100644
--- a/src/com/android/messaging/datamodel/DataModel.java
+++ b/src/com/android/messaging/datamodel/DataModel.java
@@ -25,6 +25,7 @@ import com.android.messaging.Factory;
import com.android.messaging.datamodel.action.Action;
import com.android.messaging.datamodel.action.ActionService;
import com.android.messaging.datamodel.action.BackgroundWorker;
+import com.android.messaging.datamodel.data.AudioListItemData;
import com.android.messaging.datamodel.data.BlockedParticipantsData;
import com.android.messaging.datamodel.data.BlockedParticipantsData.BlockedParticipantsDataListener;
import com.android.messaging.datamodel.data.ContactListItemData;
@@ -84,6 +85,8 @@ public abstract class DataModel {
public abstract GalleryGridItemData createGalleryGridItemData();
+ public abstract AudioListItemData createAudioListItemData();
+
public abstract LaunchConversationData createLaunchConversationData(
LaunchConversationDataListener listener);
diff --git a/src/com/android/messaging/datamodel/DataModelImpl.java b/src/com/android/messaging/datamodel/DataModelImpl.java
index 6ab3f00..2f5b377 100644
--- a/src/com/android/messaging/datamodel/DataModelImpl.java
+++ b/src/com/android/messaging/datamodel/DataModelImpl.java
@@ -25,6 +25,7 @@ import com.android.messaging.datamodel.action.ActionService;
import com.android.messaging.datamodel.action.BackgroundWorker;
import com.android.messaging.datamodel.action.FixupMessageStatusOnStartupAction;
import com.android.messaging.datamodel.action.ProcessPendingMessagesAction;
+import com.android.messaging.datamodel.data.AudioListItemData;
import com.android.messaging.datamodel.data.BlockedParticipantsData;
import com.android.messaging.datamodel.data.BlockedParticipantsData.BlockedParticipantsDataListener;
import com.android.messaging.datamodel.data.ContactListItemData;
@@ -115,6 +116,11 @@ public class DataModelImpl extends DataModel {
}
@Override
+ public AudioListItemData createAudioListItemData() {
+ return new AudioListItemData();
+ }
+
+ @Override
public LaunchConversationData createLaunchConversationData(
final LaunchConversationDataListener listener) {
return new LaunchConversationData(listener);
diff --git a/src/com/android/messaging/datamodel/DatabaseHelper.java b/src/com/android/messaging/datamodel/DatabaseHelper.java
index 2025e2c..5bfca06 100644
--- a/src/com/android/messaging/datamodel/DatabaseHelper.java
+++ b/src/com/android/messaging/datamodel/DatabaseHelper.java
@@ -181,9 +181,9 @@ public class DatabaseHelper extends SQLiteOpenHelper {
+ ConversationColumns.OTHER_PARTICIPANT_NORMALIZED_DESTINATION + " TEXT, "
+ ConversationColumns.CURRENT_SELF_ID + " TEXT, "
+ ConversationColumns.PARTICIPANT_COUNT + " INT DEFAULT(0), "
- + ConversationColumns.NOTIFICATION_ENABLED + " INT DEFAULT(1), "
+ + ConversationColumns.NOTIFICATION_ENABLED + " INT DEFAULT(-1), "
+ ConversationColumns.NOTIFICATION_SOUND_URI + " TEXT, "
- + ConversationColumns.NOTIFICATION_VIBRATION + " INT DEFAULT(1), "
+ + ConversationColumns.NOTIFICATION_VIBRATION + " INT DEFAULT(-1), "
+ ConversationColumns.INCLUDE_EMAIL_ADDRESS + " INT DEFAULT(0), "
+ ConversationColumns.SMS_SERVICE_CENTER + " TEXT ,"
+ ConversationColumns.IS_ENTERPRISE + " INT DEFAULT(0)"
diff --git a/src/com/android/messaging/datamodel/GalleryBoundCursorLoader.java b/src/com/android/messaging/datamodel/GalleryBoundCursorLoader.java
index 28ec303..2816bab 100644
--- a/src/com/android/messaging/datamodel/GalleryBoundCursorLoader.java
+++ b/src/com/android/messaging/datamodel/GalleryBoundCursorLoader.java
@@ -33,9 +33,10 @@ public class GalleryBoundCursorLoader extends BoundCursorLoader {
public static final String MEDIA_SCANNER_VOLUME_EXTERNAL = "external";
private static final Uri STORAGE_URI = Files.getContentUri(MEDIA_SCANNER_VOLUME_EXTERNAL);
private static final String SORT_ORDER = Media.DATE_MODIFIED + " DESC";
+ private static final String TAG = GalleryBoundCursorLoader.class.getSimpleName();
private static final String IMAGE_SELECTION = createSelection(
MessagePartData.ACCEPTABLE_IMAGE_TYPES,
- new Integer[] { FileColumns.MEDIA_TYPE_IMAGE });
+ new Integer[] { FileColumns.MEDIA_TYPE_IMAGE, FileColumns.MEDIA_TYPE_VIDEO});
public GalleryBoundCursorLoader(final String bindingId, final Context context) {
super(bindingId, context, STORAGE_URI, GalleryGridItemData.IMAGE_PROJECTION,
diff --git a/src/com/android/messaging/datamodel/action/BugleActionToasts.java b/src/com/android/messaging/datamodel/action/BugleActionToasts.java
index f60facd..17d15f2 100644
--- a/src/com/android/messaging/datamodel/action/BugleActionToasts.java
+++ b/src/com/android/messaging/datamodel/action/BugleActionToasts.java
@@ -114,7 +114,6 @@ public class BugleActionToasts {
}
public static void onConversationDeleted() {
- showToast(R.string.conversation_deleted);
}
private static void showToast(final int messageResId) {
diff --git a/src/com/android/messaging/datamodel/action/ProcessPendingMessagesAction.java b/src/com/android/messaging/datamodel/action/ProcessPendingMessagesAction.java
index 8a41f4a..ecbec10 100644
--- a/src/com/android/messaging/datamodel/action/ProcessPendingMessagesAction.java
+++ b/src/com/android/messaging/datamodel/action/ProcessPendingMessagesAction.java
@@ -24,12 +24,14 @@ import android.net.ConnectivityManager;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
import com.android.messaging.Factory;
import com.android.messaging.datamodel.BugleDatabaseOperations;
import com.android.messaging.datamodel.DataModel;
import com.android.messaging.datamodel.DatabaseHelper;
import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
+import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
import com.android.messaging.datamodel.DatabaseWrapper;
import com.android.messaging.datamodel.MessagingContentProvider;
import com.android.messaging.datamodel.data.MessageData;
@@ -45,6 +47,7 @@ import com.android.messaging.util.OsUtil;
import com.android.messaging.util.PhoneUtils;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
/**
@@ -218,13 +221,15 @@ public class ProcessPendingMessagesAction extends Action implements Parcelable {
final DatabaseWrapper db = DataModel.get().getDatabase();
final long now = System.currentTimeMillis();
- final String toSendMessageId = findNextMessageToSend(db, now);
- if (toSendMessageId != null) {
- return true;
- } else {
- final String toDownloadMessageId = findNextMessageToDownload(db, now);
- if (toDownloadMessageId != null) {
+ for (int subId : getActiveSubscriptionIds()) {
+ final String toSendMessageId = findNextMessageToSend(db, now, subId);
+ if (toSendMessageId != null) {
return true;
+ } else {
+ final String toDownloadMessageId = findNextMessageToDownload(db, now, subId);
+ if (toDownloadMessageId != null) {
+ return true;
+ }
}
}
// Messages may be in the process of sending/downloading even when there are no pending
@@ -232,6 +237,21 @@ public class ProcessPendingMessagesAction extends Action implements Parcelable {
return false;
}
+ private static int[] getActiveSubscriptionIds() {
+ if (!OsUtil.isAtLeastL_MR1()) {
+ return new int[] { ParticipantData.DEFAULT_SELF_SUB_ID };
+ }
+ List<SubscriptionInfo> subscriptions = PhoneUtils.getDefault().toLMr1()
+ .getActiveSubscriptionInfoList();
+
+ int numSubs = subscriptions.size();
+ int[] result = new int[numSubs];
+ for (int i = 0; i < numSubs; i++) {
+ result[i] = subscriptions.get(i).getSubscriptionId();
+ }
+ return result;
+ }
+
/**
* Queue any pending actions
* @param actionState
@@ -240,37 +260,44 @@ public class ProcessPendingMessagesAction extends Action implements Parcelable {
private boolean queueActions(final Action processingAction) {
final DatabaseWrapper db = DataModel.get().getDatabase();
final long now = System.currentTimeMillis();
- boolean succeeded = true;
+ boolean succeeded = false;
- // Will queue no more than one message to send plus one message to download
+ // Will queue no more than one message per subscription to send plus one message to download
// This keeps outgoing messages "in order" but allow downloads to happen even if sending
// gets blocked until messages time out. Manual resend bumps messages to head of queue.
- final String toSendMessageId = findNextMessageToSend(db, now);
- final String toDownloadMessageId = findNextMessageToDownload(db, now);
- if (toSendMessageId != null) {
- LogUtil.i(TAG, "ProcessPendingMessagesAction: Queueing message " + toSendMessageId
- + " for sending");
- // This could queue nothing
- if (!SendMessageAction.queueForSendInBackground(toSendMessageId, processingAction)) {
- LogUtil.w(TAG, "ProcessPendingMessagesAction: Failed to queue message "
- + toSendMessageId + " for sending");
- succeeded = false;
+ for (int subId : getActiveSubscriptionIds()) {
+ final String toSendMessageId = findNextMessageToSend(db, now, subId);
+ final String toDownloadMessageId = findNextMessageToDownload(db, now, subId);
+ if (toSendMessageId != null) {
+ LogUtil.i(TAG, "ProcessPendingMessagesAction: Queueing message " + toSendMessageId
+ + " for sending");
+ // This could queue nothing
+ if (!SendMessageAction.queueForSendInBackground(toSendMessageId,
+ processingAction)) {
+ LogUtil.w(TAG, "ProcessPendingMessagesAction: Failed to queue message "
+ + toSendMessageId + " for sending");
+ } else {
+ succeeded = true;
+ }
}
- }
- if (toDownloadMessageId != null) {
- LogUtil.i(TAG, "ProcessPendingMessagesAction: Queueing message " + toDownloadMessageId
- + " for download");
- // This could queue nothing
- if (!DownloadMmsAction.queueMmsForDownloadInBackground(toDownloadMessageId,
- processingAction)) {
- LogUtil.w(TAG, "ProcessPendingMessagesAction: Failed to queue message "
+ if (toDownloadMessageId != null) {
+ LogUtil.i(TAG, "ProcessPendingMessagesAction: Queueing message "
+ toDownloadMessageId + " for download");
- succeeded = false;
+ // This could queue nothing
+ if (!DownloadMmsAction.queueMmsForDownloadInBackground(toDownloadMessageId,
+ processingAction)) {
+ LogUtil.w(TAG, "ProcessPendingMessagesAction: Failed to queue message "
+ + toDownloadMessageId + " for download");
+ } else {
+ succeeded = true;
+ }
+
}
- }
- if (toSendMessageId == null && toDownloadMessageId == null) {
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "ProcessPendingMessagesAction: No messages to send or download");
+ if (toSendMessageId == null && toDownloadMessageId == null) {
+ if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
+ LogUtil.d(TAG, "ProcessPendingMessagesAction: No messages to send or download");
+ }
+ succeeded = true;
}
}
return succeeded;
@@ -293,7 +320,21 @@ public class ProcessPendingMessagesAction extends Action implements Parcelable {
return null;
}
- private static String findNextMessageToSend(final DatabaseWrapper db, final long now) {
+ private static String prefixColumnWithTable(final String tableName, final String column) {
+ return tableName + "." + column;
+ }
+
+ private static String[] prefixProjectionWithTable(final String tableName,
+ final String[] projection) {
+ String[] result = new String[projection.length];
+ for (int i = 0; i < projection.length; i++) {
+ result[i] = prefixColumnWithTable(tableName, projection[i]);
+ }
+ return result;
+ }
+
+ private static String findNextMessageToSend(final DatabaseWrapper db, final long now,
+ final int subId) {
String toSendMessageId = null;
db.beginTransaction();
Cursor sending = null;
@@ -302,12 +343,25 @@ public class ProcessPendingMessagesAction extends Action implements Parcelable {
int pendingCnt = 0;
int failedCnt = 0;
try {
+ String[] projection = prefixProjectionWithTable(DatabaseHelper.MESSAGES_TABLE,
+ MessageData.getProjection());
+ String subIdClause =
+ prefixColumnWithTable(DatabaseHelper.MESSAGES_TABLE,
+ MessageColumns.SELF_PARTICIPANT_ID)
+ + " = "
+ + prefixColumnWithTable(DatabaseHelper.PARTICIPANTS_TABLE,
+ ParticipantColumns._ID)
+ + " AND " + ParticipantColumns.SUB_ID + " =?";
+
// First check to see if we have any messages already sending
- sending = db.query(DatabaseHelper.MESSAGES_TABLE,
- MessageData.getProjection(),
- DatabaseHelper.MessageColumns.STATUS + " IN (?, ?)",
+ sending = db.query(DatabaseHelper.MESSAGES_TABLE + ","
+ + DatabaseHelper.PARTICIPANTS_TABLE,
+ projection,
+ DatabaseHelper.MessageColumns.STATUS + " IN (?, ?)"
+ + " AND " + subIdClause,
new String[]{Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_SENDING),
- Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_RESENDING)},
+ Integer.toString(MessageData.BUGLE_STATUS_OUTGOING_RESENDING),
+ Integer.toString(subId)},
null,
null,
DatabaseHelper.MessageColumns.RECEIVED_TIMESTAMP + " ASC");
@@ -317,12 +371,14 @@ public class ProcessPendingMessagesAction extends Action implements Parcelable {
final ContentValues values = new ContentValues();
values.put(DatabaseHelper.MessageColumns.STATUS,
MessageData.BUGLE_STATUS_OUTGOING_FAILED);
- cursor = db.query(DatabaseHelper.MESSAGES_TABLE,
- MessageData.getProjection(),
+ cursor = db.query(DatabaseHelper.MESSAGES_TABLE + ","
+ + DatabaseHelper.PARTICIPANTS_TABLE,
+ projection,
DatabaseHelper.MessageColumns.STATUS + " IN ("
+ MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND + ","
- + MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY + ")",
- null,
+ + MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY + ")"
+ + " AND " + subIdClause,
+ new String[]{Integer.toString(subId)},
null,
null,
DatabaseHelper.MessageColumns.RECEIVED_TIMESTAMP + " ASC");
@@ -388,31 +444,49 @@ public class ProcessPendingMessagesAction extends Action implements Parcelable {
return toSendMessageId;
}
- private static String findNextMessageToDownload(final DatabaseWrapper db, final long now) {
+ private static String findNextMessageToDownload(final DatabaseWrapper db, final long now,
+ final int subId) {
String toDownloadMessageId = null;
db.beginTransaction();
Cursor cursor = null;
int downloadingCnt = 0;
int pendingCnt = 0;
try {
+ String[] projection = prefixProjectionWithTable(DatabaseHelper.MESSAGES_TABLE,
+ MessageData.getProjection());
+ String subIdClause =
+ prefixColumnWithTable(DatabaseHelper.MESSAGES_TABLE,
+ MessageColumns.SELF_PARTICIPANT_ID)
+ + " = "
+ + prefixColumnWithTable(DatabaseHelper.PARTICIPANTS_TABLE,
+ ParticipantColumns._ID)
+ + " AND " + ParticipantColumns.SUB_ID + " =?";
+
// First check if we have any messages already downloading
- downloadingCnt = (int) db.queryNumEntries(DatabaseHelper.MESSAGES_TABLE,
- DatabaseHelper.MessageColumns.STATUS + " IN (?, ?)",
+ downloadingCnt = (int) db.queryNumEntries(DatabaseHelper.MESSAGES_TABLE
+ + "," + DatabaseHelper.PARTICIPANTS_TABLE,
+ DatabaseHelper.MessageColumns.STATUS + " IN (?, ?)"
+ + " AND " + subIdClause,
new String[] {
Integer.toString(MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING),
- Integer.toString(MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING)
+ Integer.toString(MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING),
+ Integer.toString(subId)
});
// TODO: This query is not actually needed if downloadingCnt == 0.
- cursor = db.query(DatabaseHelper.MESSAGES_TABLE,
- MessageData.getProjection(),
+ cursor = db.query(DatabaseHelper.MESSAGES_TABLE + ","
+ + DatabaseHelper.PARTICIPANTS_TABLE,
+ projection,
DatabaseHelper.MessageColumns.STATUS + " =? OR "
- + DatabaseHelper.MessageColumns.STATUS + " =?",
+ + DatabaseHelper.MessageColumns.STATUS + " =?"
+ + " AND " + subIdClause,
new String[]{
Integer.toString(
MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD),
Integer.toString(
- MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD)
+ MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD),
+ Integer.toString(
+ subId)
},
null,
null,
diff --git a/src/com/android/messaging/datamodel/data/AudioListItemData.java b/src/com/android/messaging/datamodel/data/AudioListItemData.java
new file mode 100644
index 0000000..e069c80
--- /dev/null
+++ b/src/com/android/messaging/datamodel/data/AudioListItemData.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.messaging.datamodel.data;
+
+import android.database.Cursor;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.provider.BaseColumns;
+import android.provider.MediaStore.Images.Media;
+import android.text.TextUtils;
+import com.android.messaging.util.UriUtil;
+
+/**
+ * Provides data for GalleryGridItemView
+ */
+public class AudioListItemData {
+ private static final String TAG = AudioListItemData.class.getSimpleName();
+ public static final String[] AUDIO_PROJECTION = new String[] {
+ Media._ID,
+ Media.DATA,
+ Media.MIME_TYPE,
+ Media.DATE_MODIFIED};
+
+ public static final String[] SPECIAL_ITEM_COLUMNS = new String[] {
+ BaseColumns._ID
+ };
+
+ private static final int INDEX_ID = 0;
+
+ // For local image gallery.
+ private static final int INDEX_DATA_PATH = 1;
+ private static final int INDEX_MIME_TYPE = 2;
+ private static final int INDEX_DATE_MODIFIED = 3;
+
+ private Uri mAudioUri;
+ private String mContentType;
+ private long mDateSeconds;
+
+ public AudioListItemData() {
+ }
+
+ public void bind(final Cursor cursor) {
+ mContentType = cursor.getString(INDEX_MIME_TYPE);
+ final String dateModified = cursor.getString(INDEX_DATE_MODIFIED);
+ mDateSeconds = !TextUtils.isEmpty(dateModified) ? Long.parseLong(dateModified) : -1;
+ mAudioUri = UriUtil.getUriForResourceFile(cursor.getString(INDEX_DATA_PATH));
+ }
+
+ public Uri getAudioUri() {
+ return mAudioUri;
+ }
+
+ public String getAudioFilename() {
+ if (mAudioUri != null) {
+ return mAudioUri.getLastPathSegment();
+ } else {
+ return "";
+ }
+ }
+
+ public MessagePartData constructMessagePartData(final Rect startRect) {
+ return new MediaPickerMessagePartData(startRect, mContentType,
+ mAudioUri, 0, 0);
+ }
+
+ /**
+ * @return The date in seconds. This can be negative if we could not retreive date info
+ */
+ public long getDateSeconds() {
+ return mDateSeconds;
+ }
+
+ public String getContentType() {
+ return mContentType;
+ }
+}
diff --git a/src/com/android/messaging/datamodel/data/ConversationListItemData.java b/src/com/android/messaging/datamodel/data/ConversationListItemData.java
index f627a09..13cfc74 100644
--- a/src/com/android/messaging/datamodel/data/ConversationListItemData.java
+++ b/src/com/android/messaging/datamodel/data/ConversationListItemData.java
@@ -30,6 +30,7 @@ import com.android.messaging.datamodel.action.DeleteConversationAction;
import com.android.messaging.util.Assert;
import com.android.messaging.util.ContactUtil;
import com.android.messaging.util.Dates;
+import com.android.messaging.util.NotificationUtil;
import com.google.common.base.Joiner;
import java.util.ArrayList;
@@ -92,9 +93,11 @@ public class ConversationListItemData {
INDEX_OTHER_PARTICIPANT_NORMALIZED_DESTINATION);
mSelfId = cursor.getString(INDEX_SELF_ID);
mParticipantCount = cursor.getInt(INDEX_PARTICIPANT_COUNT);
- mNotificationEnabled = cursor.getInt(INDEX_NOTIFICATION_ENABLED) == 1;
+ mNotificationEnabled = NotificationUtil.getConversationNotificationEnabled
+ (cursor.getInt(INDEX_NOTIFICATION_ENABLED));
mNotificationSoundUri = cursor.getString(INDEX_NOTIFICATION_SOUND_URI);
- mNotificationVibrate = cursor.getInt(INDEX_NOTIFICATION_VIBRATION) == 1;
+ mNotificationVibrate = NotificationUtil.getConversationNotificationVibrateEnabled(
+ cursor.getInt(INDEX_NOTIFICATION_VIBRATION));
mIncludeEmailAddress = cursor.getInt(INDEX_INCLUDE_EMAIL_ADDRESS) == 1;
mMessageStatus = cursor.getInt(INDEX_MESSAGE_STATUS);
mMessageRawTelephonyStatus = cursor.getInt(INDEX_MESSAGE_RAW_TELEPHONY_STATUS);
diff --git a/src/com/android/messaging/datamodel/data/GalleryGridItemData.java b/src/com/android/messaging/datamodel/data/GalleryGridItemData.java
index 6649757..a83189e 100644
--- a/src/com/android/messaging/datamodel/data/GalleryGridItemData.java
+++ b/src/com/android/messaging/datamodel/data/GalleryGridItemData.java
@@ -19,6 +19,8 @@ package com.android.messaging.datamodel.data;
import android.database.Cursor;
import android.graphics.Rect;
import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Handler;
import android.provider.BaseColumns;
import android.provider.MediaStore.Images.Media;
import android.text.TextUtils;
@@ -26,7 +28,9 @@ import android.text.TextUtils;
import com.android.messaging.datamodel.media.FileImageRequestDescriptor;
import com.android.messaging.datamodel.media.ImageRequest;
import com.android.messaging.datamodel.media.UriImageRequestDescriptor;
+import com.android.messaging.datamodel.media.VideoThumbnailRequestDescriptor;
import com.android.messaging.util.Assert;
+import com.android.messaging.util.UriUtil;
/**
* Provides data for GalleryGridItemView
@@ -59,7 +63,9 @@ public class GalleryGridItemData {
private UriImageRequestDescriptor mImageData;
private String mContentType;
private boolean mIsDocumentPickerItem;
+ private boolean mIsVideoItem;
private long mDateSeconds;
+ private long mContentSize = 0;
public GalleryGridItemData() {
}
@@ -67,10 +73,15 @@ public class GalleryGridItemData {
public void bind(final Cursor cursor, final int desiredWidth, final int desiredHeight) {
mIsDocumentPickerItem = TextUtils.equals(cursor.getString(INDEX_ID),
ID_DOCUMENT_PICKER_ITEM);
+
if (mIsDocumentPickerItem) {
mImageData = null;
mContentType = null;
} else {
+
+ String mimeType = (cursor.getString(INDEX_MIME_TYPE));
+ mIsVideoItem = (mimeType != null && mimeType.toLowerCase().contains("video/"));
+
int sourceWidth = cursor.getInt(INDEX_WIDTH);
int sourceHeight = cursor.getInt(INDEX_HEIGHT);
@@ -85,22 +96,60 @@ public class GalleryGridItemData {
mContentType = cursor.getString(INDEX_MIME_TYPE);
final String dateModified = cursor.getString(INDEX_DATE_MODIFIED);
mDateSeconds = !TextUtils.isEmpty(dateModified) ? Long.parseLong(dateModified) : -1;
- mImageData = new FileImageRequestDescriptor(
- cursor.getString(INDEX_DATA_PATH),
- desiredWidth,
- desiredHeight,
- sourceWidth,
- sourceHeight,
- true /* canUseThumbnail */,
- true /* allowCompression */,
- true /* isStatic */);
+ if (mIsVideoItem) {
+ mImageData = new VideoThumbnailRequestDescriptor(
+ cursor.getLong(INDEX_ID),
+ cursor.getString(INDEX_DATA_PATH),
+ desiredWidth,
+ desiredHeight,
+ sourceWidth,
+ sourceHeight);
+ } else {
+ mImageData = new FileImageRequestDescriptor(
+ cursor.getString(INDEX_DATA_PATH),
+ desiredWidth,
+ desiredHeight,
+ sourceWidth,
+ sourceHeight,
+ true /* canUseThumbnail */,
+ true /* allowCompression */,
+ true /* isStatic */);
+ }
+
+ // the the size of the image in the background, needed for selection size checking
+ // preload here in the background, so that it's ready when the thumb is clicked on
+ // TODO - remove when video compression is added, as no size check will be performed
+ // TODO - at selection time
+ if (mIsVideoItem) {
+ new AsyncTask<Void, Void, Long>() {
+ @Override
+ protected Long doInBackground(Void... params) {
+ Long size = UriUtil.getContentSize(getImageUri());
+ return size;
+ }
+
+ @Override
+ protected void onPostExecute(Long result) {
+ mContentSize = result;
+ }
+ }.execute();
+ }
+
}
}
+ public long getContentSize() {
+ return mContentSize;
+ }
+
public boolean isDocumentPickerItem() {
return mIsDocumentPickerItem;
}
+ public boolean isVideoItem() {
+ return mIsVideoItem;
+ }
+
public Uri getImageUri() {
return mImageData.uri;
}
diff --git a/src/com/android/messaging/datamodel/data/MediaPickerData.java b/src/com/android/messaging/datamodel/data/MediaPickerData.java
index b0c8bf7..8de663a 100644
--- a/src/com/android/messaging/datamodel/data/MediaPickerData.java
+++ b/src/com/android/messaging/datamodel/data/MediaPickerData.java
@@ -23,10 +23,12 @@ import android.database.Cursor;
import android.os.Bundle;
import android.support.annotation.Nullable;
+import com.android.messaging.datamodel.AudioBoundCursorLoader;
import com.android.messaging.datamodel.BoundCursorLoader;
import com.android.messaging.datamodel.GalleryBoundCursorLoader;
import com.android.messaging.datamodel.binding.BindableData;
import com.android.messaging.datamodel.binding.BindingBase;
+import com.android.messaging.ui.mediapicker.MediaPicker;
import com.android.messaging.util.Assert;
import com.android.messaging.util.BuglePrefs;
import com.android.messaging.util.BuglePrefsKeys;
@@ -44,7 +46,9 @@ public class MediaPickerData extends BindableData {
private final Context mContext;
private LoaderManager mLoaderManager;
private final GalleryLoaderCallbacks mGalleryLoaderCallbacks;
- private MediaPickerDataListener mListener;
+ private MediaPickerDataListener mImageListener;
+ private MediaPickerDataListener mAudioListener;
+ private static final String TAG = MediaPickerData.class.getSimpleName();
public MediaPickerData(final Context context) {
mContext = context;
@@ -52,6 +56,7 @@ public class MediaPickerData extends BindableData {
}
public static final int GALLERY_IMAGE_LOADER = 1;
+ public static final int GALLERY_AUDIO_LOADER = 2;
/**
* A trampoline class so that we can inherit from LoaderManager.LoaderCallbacks multiple times.
@@ -66,6 +71,9 @@ public class MediaPickerData extends BindableData {
case GALLERY_IMAGE_LOADER:
return new GalleryBoundCursorLoader(bindingId, mContext);
+ case GALLERY_AUDIO_LOADER:
+ return new AudioBoundCursorLoader(bindingId, mContext);
+
default:
Assert.fail("Unknown loader id for gallery picker!");
break;
@@ -85,10 +93,15 @@ public class MediaPickerData extends BindableData {
if (isBound(cursorLoader.getBindingId())) {
switch (loader.getId()) {
case GALLERY_IMAGE_LOADER:
- mListener.onMediaPickerDataUpdated(MediaPickerData.this, data,
+ mImageListener.onMediaPickerDataUpdated(MediaPickerData.this, data,
GALLERY_IMAGE_LOADER);
break;
+ case GALLERY_AUDIO_LOADER:
+ mAudioListener.onMediaPickerDataUpdated(MediaPickerData.this, data,
+ GALLERY_AUDIO_LOADER);
+ break;
+
default:
Assert.fail("Unknown loader id for gallery picker!");
break;
@@ -107,10 +120,15 @@ public class MediaPickerData extends BindableData {
if (isBound(cursorLoader.getBindingId())) {
switch (loader.getId()) {
case GALLERY_IMAGE_LOADER:
- mListener.onMediaPickerDataUpdated(MediaPickerData.this, null,
+ mImageListener.onMediaPickerDataUpdated(MediaPickerData.this, null,
GALLERY_IMAGE_LOADER);
break;
+ case GALLERY_AUDIO_LOADER:
+ mAudioListener.onMediaPickerDataUpdated(MediaPickerData.this, null,
+ GALLERY_AUDIO_LOADER);
+ break;
+
default:
Assert.fail("Unknown loader id for media picker!");
break;
@@ -131,10 +149,13 @@ public class MediaPickerData extends BindableData {
args.putString(BINDING_ID, binding.getBindingId());
if (loaderId == GALLERY_IMAGE_LOADER) {
mLoaderManager.initLoader(loaderId, args, mGalleryLoaderCallbacks).forceLoad();
- } else {
+ mImageListener = listener;
+ } else if (loaderId == GALLERY_AUDIO_LOADER) {
+ mLoaderManager.initLoader(loaderId, args, mGalleryLoaderCallbacks).forceLoad();
+ mAudioListener = listener;
+ }else {
Assert.fail("Unsupported loader id for media picker!");
}
- mListener = listener;
}
public void destroyLoader(final int loaderId) {
@@ -150,6 +171,7 @@ public class MediaPickerData extends BindableData {
// This could be null if we bind but the caller doesn't init the BindableData
if (mLoaderManager != null) {
mLoaderManager.destroyLoader(GALLERY_IMAGE_LOADER);
+ mLoaderManager.destroyLoader(GALLERY_AUDIO_LOADER);
mLoaderManager = null;
}
}
diff --git a/src/com/android/messaging/datamodel/data/MessagePartData.java b/src/com/android/messaging/datamodel/data/MessagePartData.java
index fffaca8..b4b74f8 100644
--- a/src/com/android/messaging/datamodel/data/MessagePartData.java
+++ b/src/com/android/messaging/datamodel/data/MessagePartData.java
@@ -53,8 +53,33 @@ import java.util.concurrent.TimeUnit;
public class MessagePartData implements Parcelable {
public static final int UNSPECIFIED_SIZE = MessagingContentProvider.UNSPECIFIED_SIZE;
public static final String[] ACCEPTABLE_IMAGE_TYPES =
- new String[] { ContentType.IMAGE_JPEG, ContentType.IMAGE_JPG, ContentType.IMAGE_PNG,
- ContentType.IMAGE_GIF };
+ new String[] {
+ // Images
+ ContentType.IMAGE_JPEG,
+ ContentType.IMAGE_JPG,
+ ContentType.IMAGE_PNG,
+ ContentType.IMAGE_GIF,
+
+ // Videos
+ ContentType.VIDEO_MP4,
+ ContentType.VIDEO_MPEG,
+ ContentType.VIDEO_MPEG4,
+ ContentType.VIDEO_3GP,
+ ContentType.VIDEO_3GPP,
+ ContentType.VIDEO_WEBM
+ };
+
+ public static final String[] ACCEPTABLE_AUDIO_TYPES =
+ new String[] {
+ // Audio
+ ContentType.AUDIO_AMR,
+ ContentType.AUDIO_3GPP,
+ ContentType.AUDIO_AAC,
+ ContentType.AUDIO_MP3,
+ ContentType.AUDIO_MP4,
+ ContentType.AUDIO_MPEG,
+ ContentType.AUDIO_MPEG3
+ };
private static final String[] sProjection = {
PartColumns._ID,
diff --git a/src/com/android/messaging/datamodel/data/PeopleOptionsItemData.java b/src/com/android/messaging/datamodel/data/PeopleOptionsItemData.java
index 5af6a30..b24ca55 100644
--- a/src/com/android/messaging/datamodel/data/PeopleOptionsItemData.java
+++ b/src/com/android/messaging/datamodel/data/PeopleOptionsItemData.java
@@ -24,6 +24,7 @@ import android.net.Uri;
import com.android.messaging.R;
import com.android.messaging.datamodel.data.ConversationListItemData.ConversationListViewColumns;
import com.android.messaging.util.Assert;
+import com.android.messaging.util.NotificationUtil;
import com.android.messaging.util.RingtoneUtil;
public class PeopleOptionsItemData {
@@ -78,7 +79,8 @@ public class PeopleOptionsItemData {
mItemId = settingType;
mOtherParticipant = otherParticipant;
- final boolean notificationEnabled = cursor.getInt(INDEX_NOTIFICATION_ENABLED) == 1;
+ final boolean notificationEnabled = NotificationUtil
+ .getConversationNotificationEnabled(cursor.getInt(INDEX_NOTIFICATION_ENABLED));
switch (settingType) {
case SETTING_NOTIFICATION_ENABLED:
mTitle = mContext.getString(R.string.notifications_enabled_conversation_pref_title);
@@ -104,7 +106,8 @@ public class PeopleOptionsItemData {
case SETTING_NOTIFICATION_VIBRATION:
mTitle = mContext.getString(R.string.notification_vibrate_pref_title);
- mChecked = cursor.getInt(INDEX_NOTIFICATION_VIBRATION) == 1;
+ mChecked = NotificationUtil.getConversationNotificationVibrateEnabled(
+ cursor.getInt(INDEX_NOTIFICATION_VIBRATION));
mEnabled = notificationEnabled;
break;
diff --git a/src/com/android/messaging/datamodel/media/AvatarRequest.java b/src/com/android/messaging/datamodel/media/AvatarRequest.java
index 22d5ccc..6a738c7 100644
--- a/src/com/android/messaging/datamodel/media/AvatarRequest.java
+++ b/src/com/android/messaging/datamodel/media/AvatarRequest.java
@@ -161,7 +161,7 @@ public class AvatarRequest extends UriImageRequest<AvatarRequestDescriptor> {
getBackgroundColor());
final Resources resources = mContext.getResources();
final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
- paint.setTypeface(Typeface.create("sans-serif-thin", Typeface.NORMAL));
+ paint.setTypeface(Typeface.create("sans-serif-medium", Typeface.NORMAL));
paint.setColor(resources.getColor(R.color.letter_tile_font_color));
final float letterToTileRatio = resources.getFraction(R.dimen.letter_to_tile_ratio, 1, 1);
paint.setTextSize(letterToTileRatio * minOfWidthAndHeight);
diff --git a/src/com/android/messaging/ui/ClassZeroActivity.java b/src/com/android/messaging/ui/ClassZeroActivity.java
index 129ec19..ccb15a0 100644
--- a/src/com/android/messaging/ui/ClassZeroActivity.java
+++ b/src/com/android/messaging/ui/ClassZeroActivity.java
@@ -33,6 +33,7 @@ import android.view.Window;
import com.android.messaging.R;
import com.android.messaging.datamodel.action.ReceiveSmsMessageAction;
+import com.android.messaging.datamodel.BugleNotifications;
import com.android.messaging.util.Assert;
import java.util.ArrayList;
@@ -88,6 +89,8 @@ public class ClassZeroActivity extends Activity {
return false;
}
mMessageQueue.add(messageValues);
+ // Show a notification to let the user know a new message has arrived
+ BugleNotifications.playClassZeroNotification();
return true;
}
diff --git a/src/com/android/messaging/ui/appsettings/ApplicationSettingsActivity.java b/src/com/android/messaging/ui/appsettings/ApplicationSettingsActivity.java
index 906009f..20ffebf 100644
--- a/src/com/android/messaging/ui/appsettings/ApplicationSettingsActivity.java
+++ b/src/com/android/messaging/ui/appsettings/ApplicationSettingsActivity.java
@@ -28,6 +28,7 @@ import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
import android.preference.RingtonePreference;
+import android.preference.SwitchPreference;
import android.preference.TwoStatePreference;
import android.provider.Settings;
import android.support.v4.app.NavUtils;
@@ -97,6 +98,8 @@ public class ApplicationSettingsActivity extends BugleActionBarActivity {
private String mSmsEnabledPrefKey;
private Preference mSmsEnabledPreference;
private boolean mIsSmsPreferenceClicked;
+ private String mSwipeRightToDeleteConversationkey;
+ private SwitchPreference mSwipeRightToDeleteConversationPreference;
public ApplicationSettingsFragment() {
// Required empty constructor
@@ -121,6 +124,10 @@ public class ApplicationSettingsActivity extends BugleActionBarActivity {
mSmsDisabledPreference = findPreference(mSmsDisabledPrefKey);
mSmsEnabledPrefKey = getString(R.string.sms_enabled_pref_key);
mSmsEnabledPreference = findPreference(mSmsEnabledPrefKey);
+ mSwipeRightToDeleteConversationkey = getString(
+ R.string.swipe_right_deletes_conversation_key);
+ mSwipeRightToDeleteConversationPreference =
+ (SwitchPreference) findPreference(mSwipeRightToDeleteConversationkey);
mIsSmsPreferenceClicked = false;
final SharedPreferences prefs = getPreferenceScreen().getSharedPreferences();
diff --git a/src/com/android/messaging/ui/conversation/ComposeMessageView.java b/src/com/android/messaging/ui/conversation/ComposeMessageView.java
index 17f8f74..a2e9a95 100644
--- a/src/com/android/messaging/ui/conversation/ComposeMessageView.java
+++ b/src/com/android/messaging/ui/conversation/ComposeMessageView.java
@@ -25,6 +25,7 @@ import android.text.Editable;
import android.text.Html;
import android.text.InputFilter;
import android.text.InputFilter.LengthFilter;
+import android.text.InputType;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
@@ -69,6 +70,8 @@ import com.android.messaging.util.MediaUtil;
import com.android.messaging.util.OsUtil;
import com.android.messaging.util.UiUtils;
+import com.cyanogenmod.messaging.util.PrefsUtils;
+
import java.util.Collection;
import java.util.List;
@@ -213,6 +216,14 @@ public class ComposeMessageView extends LinearLayout
new LengthFilter(MmsConfig.get(ParticipantData.DEFAULT_SELF_SUB_ID)
.getMaxTextLimit()) });
+ if (PrefsUtils.isShowEmoticonsEnabled()) {
+ mComposeEditText.setInputType(mComposeEditText.getInputType()
+ | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE);
+ } else {
+ mComposeEditText.setInputType(mComposeEditText.getInputType()
+ & ~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE);
+ }
+
mSelfSendIcon = (SimIconView) findViewById(R.id.self_send_icon);
mSelfSendIcon.setOnClickListener(new OnClickListener() {
@Override
diff --git a/src/com/android/messaging/ui/conversation/ConversationFragment.java b/src/com/android/messaging/ui/conversation/ConversationFragment.java
index a6a191a..2631118 100644
--- a/src/com/android/messaging/ui/conversation/ConversationFragment.java
+++ b/src/com/android/messaging/ui/conversation/ConversationFragment.java
@@ -191,7 +191,8 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_SELF_ID);
Assert.notNull(conversationId);
Assert.notNull(selfId);
- if (TextUtils.equals(mBinding.getData().getConversationId(), conversationId)) {
+ if (isBound() && TextUtils
+ .equals(mBinding.getData().getConversationId(), conversationId)) {
mComposeMessageView.updateConversationSelfIdOnExternalChange(selfId);
}
}
@@ -564,6 +565,7 @@ public class ConversationFragment extends Fragment implements ConversationDataLi
@Override
public void run() {
view.setAlpha(1);
+ dispatchAddFinished(holder);
}
});
mPopupTransitionAnimation.startAfterLayoutComplete();
diff --git a/src/com/android/messaging/ui/conversationlist/ConversationListItemView.java b/src/com/android/messaging/ui/conversationlist/ConversationListItemView.java
index 6b02eb3..f8920e0 100644
--- a/src/com/android/messaging/ui/conversationlist/ConversationListItemView.java
+++ b/src/com/android/messaging/ui/conversationlist/ConversationListItemView.java
@@ -58,6 +58,7 @@ import com.android.messaging.util.PhoneUtils;
import com.android.messaging.util.Typefaces;
import com.android.messaging.util.UiUtils;
import com.android.messaging.util.UriUtil;
+import com.cyanogenmod.messaging.util.PrefsUtils;
import java.util.List;
@@ -75,6 +76,8 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
private static String sPlusOneString;
private static String sPlusNString;
+ private static final int SWIPE_DIRECTION_RIGHT = 2;
+
public interface HostInterface {
boolean isConversationSelected(final String conversationId);
void onConversationClicked(final ConversationListItemData conversationListItemData,
@@ -502,6 +505,18 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
final int notificationBellVisiblity = mData.getNotificationEnabled() ? GONE : VISIBLE;
mNotificationBellView.setVisibility(notificationBellVisiblity);
+
+ if (PrefsUtils.isSwipeRightToDeleteEnabled()) {
+ mCrossSwipeArchiveLeftImageView.setImageDrawable(getResources()
+ .getDrawable(R.drawable.ic_delete_small_dark));
+ mCrossSwipeArchiveRightImageView.setImageDrawable(getResources()
+ .getDrawable(R.drawable.ic_archive_small_dark));
+ } else {
+ mCrossSwipeArchiveLeftImageView.setImageDrawable(getResources()
+ .getDrawable(R.drawable.ic_archive_small_dark));
+ mCrossSwipeArchiveRightImageView.setImageDrawable(getResources()
+ .getDrawable(R.drawable.ic_archive_small_dark));
+ }
}
public boolean isSwipeAnimatable() {
@@ -535,10 +550,16 @@ public class ConversationListItemView extends FrameLayout implements OnClickList
}
}
- public void onSwipeComplete() {
+ public void onSwipeComplete(int swipeDirection) {
final String conversationId = mData.getConversationId();
+ if (PrefsUtils.isSwipeRightToDeleteEnabled()
+ && swipeDirection == ConversationListSwipeHelper.SWIPE_DIRECTION_RIGHT) {
+ mData.deleteConversation();
+ UiUtils.showSnackBar(getContext(), getRootView(),
+ getResources().getString(R.string.conversation_deleted));
+ return;
+ }
UpdateConversationArchiveStatusAction.archiveConversation(conversationId);
-
final Runnable undoRunnable = new Runnable() {
@Override
public void run() {
diff --git a/src/com/android/messaging/ui/conversationlist/ConversationListSwipeHelper.java b/src/com/android/messaging/ui/conversationlist/ConversationListSwipeHelper.java
index 4988259..ac2aeb0 100644
--- a/src/com/android/messaging/ui/conversationlist/ConversationListSwipeHelper.java
+++ b/src/com/android/messaging/ui/conversationlist/ConversationListSwipeHelper.java
@@ -44,9 +44,9 @@ public class ConversationListSwipeHelper implements OnItemTouchListener {
private static final float PERCENTAGE_OF_WIDTH_TO_DISMISS = 0.4f;
private static final float FLING_PERCENTAGE_OF_WIDTH_TO_DISMISS = 0.05f;
- private static final int SWIPE_DIRECTION_NONE = 0;
- private static final int SWIPE_DIRECTION_LEFT = 1;
- private static final int SWIPE_DIRECTION_RIGHT = 2;
+ public static final int SWIPE_DIRECTION_NONE = 0;
+ public static final int SWIPE_DIRECTION_LEFT = 1;
+ public static final int SWIPE_DIRECTION_RIGHT = 2;
private final RecyclerView mRecyclerView;
private final long mDefaultRestoreAnimationDuration;
@@ -269,7 +269,7 @@ public class ConversationListSwipeHelper implements OnItemTouchListener {
private void onSwipeGestureEnd(final ConversationListItemView itemView,
final int swipeDirection) {
if (swipeDirection == SWIPE_DIRECTION_RIGHT || swipeDirection == SWIPE_DIRECTION_LEFT) {
- itemView.onSwipeComplete();
+ itemView.onSwipeComplete(swipeDirection);
}
// Balances out onSwipeGestureStart.
diff --git a/src/com/android/messaging/ui/mediapicker/AudioListAdapter.java b/src/com/android/messaging/ui/mediapicker/AudioListAdapter.java
new file mode 100644
index 0000000..2520d6b
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/AudioListAdapter.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.messaging.ui.mediapicker;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CursorAdapter;
+import com.android.messaging.R;
+import com.android.messaging.ui.mediapicker.AudioListItemView.HostInterface;
+import com.android.messaging.util.Assert;
+
+/**
+ * Bridges between the image cursor loaded by GalleryBoundCursorLoader and the GalleryGridView.
+ */
+public class AudioListAdapter extends CursorAdapter {
+ private HostInterface mGgivHostInterface;
+
+ public AudioListAdapter(final Context context, final Cursor cursor) {
+ super(context, cursor, 0);
+ }
+
+ public void setHostInterface(final HostInterface ggivHostInterface) {
+ mGgivHostInterface = ggivHostInterface;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void bindView(final View view, final Context context, final Cursor cursor) {
+ Assert.isTrue(view instanceof AudioListItemView);
+ final AudioListItemView audioListItemView = (AudioListItemView) view;
+ audioListItemView.bind(cursor, mGgivHostInterface);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public View newView(final Context context, final Cursor cursor, final ViewGroup parent) {
+ final LayoutInflater layoutInflater = LayoutInflater.from(context);
+ return layoutInflater.inflate(R.layout.audio_list_item_view, parent, false);
+ }
+}
diff --git a/src/com/android/messaging/ui/mediapicker/AudioListChooser.java b/src/com/android/messaging/ui/mediapicker/AudioListChooser.java
new file mode 100644
index 0000000..de0445c
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/AudioListChooser.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.messaging.ui.mediapicker;
+
+import android.Manifest;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.support.v7.app.ActionBar;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.data.MediaPickerData;
+import com.android.messaging.datamodel.data.MediaPickerData.MediaPickerDataListener;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.OsUtil;
+
+/**
+ * Chooser which allows the user to select one or more existing audios
+ */
+class AudioListChooser extends MediaChooser implements
+ AudioListView.AudioListViewListener, MediaPickerDataListener {
+ private final AudioListAdapter mAdapter;
+ private AudioListView mAudioListView;
+ private View mMissingPermissionView;
+ private static final String TAG = AudioListChooser.class.getSimpleName();
+
+ AudioListChooser(final MediaPicker mediaPicker) {
+ super(mediaPicker);
+ mAdapter = new AudioListAdapter(Factory.get().getApplicationContext(), null);
+ }
+
+ @Override
+ public int getSupportedMediaTypes() {
+ return MediaPicker.MEDIA_TYPE_AUDIO;
+ }
+
+ @Override
+ public View destroyView() {
+ mAudioListView.setAdapter(null);
+ mAdapter.setHostInterface(null);
+ // The loader is started only if startMediaPickerDataLoader() is called
+ if (OsUtil.hasStoragePermission()) {
+ mBindingRef.getData().destroyLoader(MediaPickerData.GALLERY_AUDIO_LOADER);
+ }
+ return super.destroyView();
+ }
+
+ @Override
+ public int getIconResource() {
+ return R.drawable.ic_library_music_white_24px;
+ }
+
+ @Override
+ public int getIconDescriptionResource() {
+ return R.string.mediapicker_audioChooserDescription;
+ }
+
+ @Override
+ public boolean canSwipeDown() {
+ return mAudioListView.canSwipeDown();
+ }
+
+ @Override
+ public void onItemSelected(final MessagePartData item) {
+ mMediaPicker.dispatchItemsSelected(item, !mAudioListView.isMultiSelectEnabled());
+ }
+
+ @Override
+ public void onItemUnselected(final MessagePartData item) {
+ mMediaPicker.dispatchItemUnselected(item);
+ }
+
+ @Override
+ public void onConfirmSelection() {
+ // The user may only confirm if multiselect is enabled.
+ Assert.isTrue(mAudioListView.isMultiSelectEnabled());
+ mMediaPicker.dispatchConfirmItemSelection();
+ }
+
+ @Override
+ public void onUpdate() {
+ mMediaPicker.invalidateOptionsMenu();
+ }
+
+ @Override
+ public void onCreateOptionsMenu(final MenuInflater inflater, final Menu menu) {
+ if (mView != null) {
+ mAudioListView.onCreateOptionsMenu(inflater, menu);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ return (mView != null) ? mAudioListView.onOptionsItemSelected(item) : false;
+ }
+
+ @Override
+ protected View createView(final ViewGroup container) {
+ final LayoutInflater inflater = getLayoutInflater();
+ final View view = inflater.inflate(
+ R.layout.mediapicker_audio_list_chooser,
+ container /* root */,
+ false /* attachToRoot */);
+
+ mAudioListView = (AudioListView) view.findViewById(R.id.audio_list_view);
+ mAdapter.setHostInterface(mAudioListView);
+ mAudioListView.setAdapter(mAdapter);
+ mAudioListView.setHostInterface(this);
+ mAudioListView.setDraftMessageDataModel(mMediaPicker.getDraftMessageDataModel());
+ if (OsUtil.hasStoragePermission()) {
+ startMediaPickerDataLoader();
+ }
+
+ mMissingPermissionView = view.findViewById(R.id.missing_permission_view);
+ updateForPermissionState(OsUtil.hasStoragePermission());
+ return view;
+ }
+
+ @Override
+ int getActionBarTitleResId() {
+ return R.string.mediapicker_audio_list_title;
+ }
+
+ @Override
+ void updateActionBar(final ActionBar actionBar) {
+ super.updateActionBar(actionBar);
+ if (mAudioListView == null) {
+ return;
+ }
+ final int selectionCount = mAudioListView.getSelectionCount();
+ if (selectionCount > 0 && mAudioListView.isMultiSelectEnabled()) {
+ actionBar.setTitle(getContext().getResources().getString(
+ R.string.mediapicker_audio_list_title_selection,
+ selectionCount));
+ }
+ }
+
+ @Override
+ public void onMediaPickerDataUpdated(final MediaPickerData mediaPickerData, final Object data,
+ final int loaderId) {
+ mBindingRef.ensureBound(mediaPickerData);
+ Assert.equals(MediaPickerData.GALLERY_AUDIO_LOADER, loaderId);
+ Cursor rawCursor = null;
+ if (data instanceof Cursor) {
+ rawCursor = (Cursor) data;
+ }
+
+ mAdapter.swapCursor(rawCursor);
+ }
+
+ @Override
+ public void onResume() {
+ if (OsUtil.hasStoragePermission()) {
+ // Work around a bug in MediaStore where cursors querying the Files provider don't get
+ // updated for changes to Images.Media or Video.Media.
+ startMediaPickerDataLoader();
+ updateForPermissionState(true);
+ } else {
+ updateForPermissionState(false);
+ }
+ }
+
+ @Override
+ protected void setSelected(final boolean selected) {
+ super.setSelected(selected);
+ if (selected && !OsUtil.hasStoragePermission()) {
+ mMediaPicker.requestPermissions(
+ new String[] { Manifest.permission.READ_EXTERNAL_STORAGE },
+ MediaPicker.AUDIO_LIBRARY_PERMISSION_REQUEST_CODE);
+ }
+ }
+
+ private void startMediaPickerDataLoader() {
+ mBindingRef.getData().startLoader(MediaPickerData.GALLERY_AUDIO_LOADER, mBindingRef, null,
+ this);
+ }
+
+ @Override
+ protected void onRequestPermissionsResult(
+ final int requestCode, final String permissions[], final int[] grantResults) {
+ if (requestCode == MediaPicker.AUDIO_LIBRARY_PERMISSION_REQUEST_CODE) {
+ final boolean permissionGranted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
+ if (permissionGranted) {
+ startMediaPickerDataLoader();
+ }
+ updateForPermissionState(permissionGranted);
+ }
+ }
+
+ private void updateForPermissionState(final boolean granted) {
+ // onRequestPermissionsResult can sometimes get called before createView().
+ if (mAudioListView == null) {
+ return;
+ }
+
+ mAudioListView.setVisibility(granted ? View.VISIBLE : View.GONE);
+ mMissingPermissionView.setVisibility(granted ? View.GONE : View.VISIBLE);
+ }
+}
diff --git a/src/com/android/messaging/ui/mediapicker/AudioListItemView.java b/src/com/android/messaging/ui/mediapicker/AudioListItemView.java
new file mode 100644
index 0000000..d95f1fd
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/AudioListItemView.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.messaging.ui.mediapicker;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.data.AudioListItemData;
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * Shows an item in the audio picker list view. Hosts an FileImageView with a checkbox.
+ */
+public class AudioListItemView extends LinearLayout {
+ private static final String TAG = AudioListItemView.class.getSimpleName();
+ /**
+ * Implemented by the owner of this ListItemView instance to communicate on media
+ * picking and selection events.
+ */
+ public interface HostInterface {
+ void onItemClicked(View view, AudioListItemData data, boolean longClick);
+ boolean isItemSelected(AudioListItemData data);
+ boolean isMultiSelectEnabled();
+ }
+
+ @VisibleForTesting
+ AudioListItemData mData;
+ TextView mAudioFilename;
+ CheckBox mCheckBox;
+ ImageView mImageIcon;
+
+ private HostInterface mHostInterface;
+ private final OnClickListener mOnClickListener = new OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ mHostInterface.onItemClicked(AudioListItemView.this, mData, false /*longClick*/);
+ }
+ };
+
+ public AudioListItemView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ mData = DataModel.get().createAudioListItemData();
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mAudioFilename = (TextView)findViewById(R.id.audio_filename);
+ mImageIcon = (ImageView) findViewById(R.id.audio_button);
+
+ mCheckBox = (CheckBox)findViewById(R.id.audio_checkbox);
+ mCheckBox.setOnClickListener(mOnClickListener);
+ setOnClickListener(mOnClickListener);
+
+ final OnLongClickListener longClickListener = new OnLongClickListener() {
+ @Override
+ public boolean onLongClick(final View v) {
+ mHostInterface.onItemClicked(v, mData, true /* longClick */);
+ return true;
+ }
+ };
+ setOnLongClickListener(longClickListener);
+ mCheckBox.setOnLongClickListener(longClickListener);
+ }
+
+
+ public void bind(final Cursor cursor, final HostInterface hostInterface) {
+ mData.bind(cursor);
+ mHostInterface = hostInterface;
+ updateViewState();
+ }
+
+ private void updateViewState() {
+ updateListItemView();
+ if (mHostInterface.isMultiSelectEnabled()) {
+ mCheckBox.setVisibility(VISIBLE);
+ mCheckBox.setClickable(true);
+ mCheckBox.setChecked(mHostInterface.isItemSelected(mData));
+ mImageIcon.setVisibility(GONE);
+ } else {
+ mCheckBox.setVisibility(GONE);
+ mCheckBox.setClickable(false);
+ mImageIcon.setVisibility(VISIBLE);
+ }
+ }
+
+ private void updateListItemView() {
+ mAudioFilename.setText(mData.getAudioFilename());
+ }
+
+}
diff --git a/src/com/android/messaging/ui/mediapicker/AudioListView.java b/src/com/android/messaging/ui/mediapicker/AudioListView.java
new file mode 100644
index 0000000..e097b9e
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/AudioListView.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.messaging.ui.mediapicker;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.v4.util.ArrayMap;
+import android.util.AttributeSet;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import com.android.messaging.R;
+import com.android.messaging.datamodel.binding.BindingBase;
+import com.android.messaging.datamodel.binding.ImmutableBindingRef;
+import com.android.messaging.datamodel.data.AudioListItemData;
+import com.android.messaging.datamodel.data.DraftMessageData;
+import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageDataListener;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.ui.PersistentInstanceState;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.ContentType;
+import com.android.messaging.util.LogUtil;
+
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Shows a list of audio filenames from external storage in a ListView with multi-select
+ * capabilities
+ */
+public class AudioListView extends MediaPickerListView implements
+ AudioListItemView.HostInterface,
+ PersistentInstanceState,
+ DraftMessageDataListener {
+ /**
+ * Implemented by the owner of this GalleryGridView instance to communicate on image
+ * picking and multi-image selection events.
+ */
+ public interface AudioListViewListener {
+ void onItemSelected(MessagePartData item);
+ void onItemUnselected(MessagePartData item);
+ void onConfirmSelection();
+ void onUpdate();
+ }
+
+ private AudioListViewListener mListener;
+
+ // TODO: Consider putting this into the data model object if we add more states.
+ private final ArrayMap<Uri, MessagePartData> mSelectedAudios;
+ private boolean mIsMultiSelectMode = false;
+ private ImmutableBindingRef<DraftMessageData> mDraftMessageDataModel;
+
+ public AudioListView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ mSelectedAudios = new ArrayMap<Uri, MessagePartData>();
+ }
+
+ public void setHostInterface(final AudioListViewListener hostInterface) {
+ mListener = hostInterface;
+ }
+
+ public void setDraftMessageDataModel(final BindingBase<DraftMessageData> dataModel) {
+ mDraftMessageDataModel = BindingBase.createBindingReference(dataModel);
+ mDraftMessageDataModel.getData().addListener(this);
+ }
+
+ @Override
+ public void onItemClicked(final View view, final AudioListItemData data,
+ final boolean longClick) {
+ if (ContentType.isMediaType(data.getContentType())) {
+ if (longClick) {
+ // Turn on multi-select mode when an item is long-pressed.
+ setMultiSelectEnabled(true);
+ }
+
+ final Rect startRect = new Rect();
+ view.getGlobalVisibleRect(startRect);
+ if (isMultiSelectEnabled()) {
+ toggleItemSelection(startRect, data);
+ } else {
+ mListener.onItemSelected(data.constructMessagePartData(startRect));
+ }
+ } else {
+ LogUtil.w(LogUtil.BUGLE_TAG,
+ "Selected item has invalid contentType " + data.getContentType());
+ }
+ }
+
+ @Override
+ public boolean isItemSelected(final AudioListItemData data) {
+ return mSelectedAudios.containsKey(data.getAudioUri());
+ }
+
+ int getSelectionCount() {
+ return mSelectedAudios.size();
+ }
+
+ @Override
+ public boolean isMultiSelectEnabled() {
+ return mIsMultiSelectMode;
+ }
+
+ private void toggleItemSelection(final Rect startRect, final AudioListItemData data) {
+ Assert.isTrue(isMultiSelectEnabled());
+ if (isItemSelected(data)) {
+ final MessagePartData item = mSelectedAudios.remove(data.getAudioUri());
+ mListener.onItemUnselected(item);
+ if (mSelectedAudios.size() == 0) {
+ // No image is selected any more, turn off multi-select mode.
+ setMultiSelectEnabled(false);
+ }
+ } else {
+ final MessagePartData item = data.constructMessagePartData(startRect);
+ mSelectedAudios.put(data.getAudioUri(), item);
+ mListener.onItemSelected(item);
+ }
+ invalidateViews();
+ }
+
+ private void toggleMultiSelect() {
+ mIsMultiSelectMode = !mIsMultiSelectMode;
+ invalidateViews();
+ }
+
+ private void setMultiSelectEnabled(final boolean enabled) {
+ if (mIsMultiSelectMode != enabled) {
+ toggleMultiSelect();
+ }
+ }
+
+ private boolean canToggleMultiSelect() {
+ // We allow the user to toggle multi-select mode only when nothing has selected. If
+ // something has been selected, we show a confirm button instead.
+ return mSelectedAudios.size() == 0;
+ }
+
+ public void onCreateOptionsMenu(final MenuInflater inflater, final Menu menu) {
+ inflater.inflate(R.menu.gallery_picker_menu, menu);
+ final MenuItem toggleMultiSelect = menu.findItem(R.id.action_multiselect);
+ final MenuItem confirmMultiSelect = menu.findItem(R.id.action_confirm_multiselect);
+ final boolean canToggleMultiSelect = canToggleMultiSelect();
+ toggleMultiSelect.setVisible(canToggleMultiSelect);
+ confirmMultiSelect.setVisible(!canToggleMultiSelect);
+ }
+
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.action_multiselect:
+ Assert.isTrue(canToggleMultiSelect());
+ toggleMultiSelect();
+ return true;
+
+ case R.id.action_confirm_multiselect:
+ Assert.isTrue(!canToggleMultiSelect());
+ mListener.onConfirmSelection();
+ return true;
+ }
+ return false;
+ }
+
+
+ @Override
+ public void onDraftChanged(final DraftMessageData data, final int changeFlags) {
+ mDraftMessageDataModel.ensureBound(data);
+ // Whenever attachment changed, refresh selection state to remove those that are not
+ // selected.
+ if ((changeFlags & DraftMessageData.ATTACHMENTS_CHANGED) ==
+ DraftMessageData.ATTACHMENTS_CHANGED) {
+ refreshImageSelectionStateOnAttachmentChange();
+ }
+ }
+
+ @Override
+ public void onDraftAttachmentLimitReached(final DraftMessageData data) {
+ mDraftMessageDataModel.ensureBound(data);
+ // Whenever draft attachment limit is reach, refresh selection state to remove those
+ // not actually added to draft.
+ refreshImageSelectionStateOnAttachmentChange();
+ }
+
+ @Override
+ public void onDraftAttachmentLoadFailed() {
+ // Nothing to do since the failed attachment gets removed automatically.
+ }
+
+ private void refreshImageSelectionStateOnAttachmentChange() {
+ boolean changed = false;
+ final Iterator<Map.Entry<Uri, MessagePartData>> iterator =
+ mSelectedAudios.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Map.Entry<Uri, MessagePartData> entry = iterator.next();
+ if (!mDraftMessageDataModel.getData().containsAttachment(entry.getKey())) {
+ iterator.remove();
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ mListener.onUpdate();
+ invalidateViews();
+ }
+ }
+
+ @Override // PersistentInstanceState
+ public Parcelable saveState() {
+ return onSaveInstanceState();
+ }
+
+ @Override // PersistentInstanceState
+ public void restoreState(final Parcelable restoredState) {
+ onRestoreInstanceState(restoredState);
+ invalidateViews();
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ final Parcelable superState = super.onSaveInstanceState();
+ final SavedState savedState = new SavedState(superState);
+ savedState.isMultiSelectMode = mIsMultiSelectMode;
+ savedState.selectedAudios = mSelectedAudios.values()
+ .toArray(new MessagePartData[mSelectedAudios.size()]);
+ return savedState;
+ }
+
+ @Override
+ public void onRestoreInstanceState(final Parcelable state) {
+ if (!(state instanceof SavedState)) {
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
+ final SavedState savedState = (SavedState) state;
+ super.onRestoreInstanceState(savedState.getSuperState());
+ mIsMultiSelectMode = savedState.isMultiSelectMode;
+ mSelectedAudios.clear();
+ for (int i = 0; i < savedState.selectedAudios.length; i++) {
+ final MessagePartData selectedAudio = savedState.selectedAudios[i];
+ mSelectedAudios.put(selectedAudio.getContentUri(), selectedAudio);
+ }
+ }
+
+ @Override // PersistentInstanceState
+ public void resetState() {
+ mSelectedAudios.clear();
+ mIsMultiSelectMode = false;
+ invalidateViews();
+ }
+
+ public static class SavedState extends BaseSavedState {
+ boolean isMultiSelectMode;
+ MessagePartData[] selectedAudios;
+
+ SavedState(final Parcelable superState) {
+ super(superState);
+ }
+
+ private SavedState(final Parcel in) {
+ super(in);
+ isMultiSelectMode = in.readInt() == 1 ? true : false;
+
+ // Read parts
+ final int partCount = in.readInt();
+ selectedAudios = new MessagePartData[partCount];
+ for (int i = 0; i < partCount; i++) {
+ selectedAudios[i] = ((MessagePartData) in.readParcelable(
+ MessagePartData.class.getClassLoader()));
+ }
+ }
+
+ @Override
+ public void writeToParcel(final Parcel out, final int flags) {
+ super.writeToParcel(out, flags);
+ out.writeInt(isMultiSelectMode ? 1 : 0);
+
+ // Write parts
+ out.writeInt(selectedAudios.length);
+ for (final MessagePartData image : selectedAudios) {
+ out.writeParcelable(image, flags);
+ }
+ }
+
+ public static final Creator<SavedState> CREATOR =
+ new Creator<SavedState>() {
+ @Override
+ public SavedState createFromParcel(final Parcel in) {
+ return new SavedState(in);
+ }
+ @Override
+ public SavedState[] newArray(final int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+}
diff --git a/src/com/android/messaging/ui/mediapicker/AudioMediaChooser.java b/src/com/android/messaging/ui/mediapicker/AudioMediaChooser.java
index 5d79293..3553f10 100644
--- a/src/com/android/messaging/ui/mediapicker/AudioMediaChooser.java
+++ b/src/com/android/messaging/ui/mediapicker/AudioMediaChooser.java
@@ -123,6 +123,10 @@ class AudioMediaChooser extends MediaChooser implements
final int requestCode, final String permissions[], final int[] grantResults) {
if (requestCode == MediaPicker.RECORD_AUDIO_PERMISSION_REQUEST_CODE) {
final boolean permissionGranted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
+ // onRequestPermissionsResult can sometimes get called before createView().
+ if (mEnabledView == null) {
+ return;
+ }
mEnabledView.setVisibility(permissionGranted ? View.VISIBLE : View.GONE);
mMissingPermissionView.setVisibility(permissionGranted ? View.GONE : View.VISIBLE);
}
diff --git a/src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java b/src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java
index 3d71fe6..034ca8b 100644
--- a/src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java
+++ b/src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java
@@ -18,7 +18,9 @@ package com.android.messaging.ui.mediapicker;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Rect;
+import android.support.v7.mms.CarrierConfigValuesLoader;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.TouchDelegate;
import android.view.View;
@@ -26,11 +28,16 @@ import android.widget.CheckBox;
import android.widget.FrameLayout;
import android.widget.ImageView.ScaleType;
+import android.widget.Toast;
import com.android.messaging.R;
import com.android.messaging.datamodel.DataModel;
+import com.android.messaging.datamodel.data.DraftMessageData;
import com.android.messaging.datamodel.data.GalleryGridItemData;
+import com.android.messaging.datamodel.data.ParticipantData;
+import com.android.messaging.sms.MmsConfig;
import com.android.messaging.ui.AsyncImageView;
import com.android.messaging.ui.ConversationDrawables;
+import com.android.messaging.util.UriUtil;
import com.google.common.annotations.VisibleForTesting;
import java.util.concurrent.TimeUnit;
@@ -39,6 +46,7 @@ import java.util.concurrent.TimeUnit;
* Shows an item in the gallery picker grid view. Hosts an FileImageView with a checkbox.
*/
public class GalleryGridItemView extends FrameLayout {
+ private static final String TAG = GalleryGridItemView.class.getSimpleName();
/**
* Implemented by the owner of this GalleryGridItemView instance to communicate on media
* picking and selection events.
@@ -47,17 +55,74 @@ public class GalleryGridItemView extends FrameLayout {
void onItemClicked(View view, GalleryGridItemData data, boolean longClick);
boolean isItemSelected(GalleryGridItemData data);
boolean isMultiSelectEnabled();
+ int getSubscriptionProviderSubId();
}
@VisibleForTesting
GalleryGridItemData mData;
+ private View mVideoButtonOverlayView;
private AsyncImageView mImageView;
private CheckBox mCheckBox;
private HostInterface mHostInterface;
+ private static long mTotalContentSize = 0;
+ private static long mMaxMessageSize = 0;
+
+ private boolean checkSize() {
+ if (mData.isDocumentPickerItem()) {
+ return true;
+ }
+ // only perform the check for videos, since they are not being compressed
+ // images will be compressed, so exclude them from this check
+
+ // determine the maximum message size, this will be computed only once per this class
+ if (mMaxMessageSize == 0) {
+ int subId = mHostInterface.getSubscriptionProviderSubId();
+ mMaxMessageSize = MmsConfig.get(subId).getMaxMessageSize();
+ }
+
+ long contentSize = mData.getContentSize();
+ if (mHostInterface.isMultiSelectEnabled()) {
+ if (mData.isVideoItem()) {
+ if (mHostInterface.isItemSelected(mData)) {
+ // un-selecting
+ mTotalContentSize -= contentSize;
+ if (mTotalContentSize < 0) {
+ mTotalContentSize = 0;
+ }
+ } else {
+ // selecting
+ mTotalContentSize += contentSize;
+ }
+ }
+ } else {
+ // short click or first long click
+ if (mData.isVideoItem()) {
+ mTotalContentSize = contentSize;
+ } else {
+ mTotalContentSize = 0;
+ }
+ }
+
+ if (mTotalContentSize > mMaxMessageSize) {
+ mTotalContentSize -= contentSize;
+ if (mTotalContentSize < 0) {
+ mTotalContentSize = 0;
+ }
+
+ Toast.makeText(getContext(), getContext().
+ getString(R.string.mediapicker_gallery_image_item_attachment_too_large),
+ Toast.LENGTH_LONG).show();
+ return false;
+ }
+
+ return true;
+ }
private final OnClickListener mOnClickListener = new OnClickListener() {
@Override
public void onClick(final View v) {
- mHostInterface.onItemClicked(GalleryGridItemView.this, mData, false /*longClick*/);
+ if (checkSize()) {
+ mHostInterface.onItemClicked(GalleryGridItemView.this, mData, false /*longClick*/);
+ }
}
};
@@ -69,6 +134,7 @@ public class GalleryGridItemView extends FrameLayout {
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+ mVideoButtonOverlayView = findViewById(R.id.video_button);
mImageView = (AsyncImageView) findViewById(R.id.image);
mCheckBox = (CheckBox) findViewById(R.id.checkbox);
mCheckBox.setOnClickListener(mOnClickListener);
@@ -76,7 +142,9 @@ public class GalleryGridItemView extends FrameLayout {
final OnLongClickListener longClickListener = new OnLongClickListener() {
@Override
public boolean onLongClick(final View v) {
- mHostInterface.onItemClicked(v, mData, true /* longClick */);
+ if (checkSize()) {
+ mHostInterface.onItemClicked(v, mData, true /* longClick */);
+ }
return true;
}
};
@@ -136,12 +204,26 @@ public class GalleryGridItemView extends FrameLayout {
private void updateImageView() {
if (mData.isDocumentPickerItem()) {
+ hideVideoPlayButtonOverlay();
mImageView.setScaleType(ScaleType.CENTER);
setBackgroundColor(ConversationDrawables.get().getConversationThemeColor());
mImageView.setImageResourceId(null);
mImageView.setImageResource(R.drawable.ic_photo_library_light);
mImageView.setContentDescription(getResources().getString(
R.string.pick_image_from_document_library_content_description));
+ } else if (mData.isVideoItem()) {
+ showVideoPlayButtonOverlay();
+ mImageView.setScaleType(ScaleType.CENTER_CROP);
+ setBackgroundColor(getResources().getColor(R.color.gallery_image_default_background));
+ mImageView.setImageResourceId(mData.getImageRequestDescriptor());
+ final long dateSeconds = mData.getDateSeconds();
+ final boolean isValidDate = (dateSeconds > 0);
+ final int templateId = isValidDate ?
+ R.string.mediapicker_gallery_image_item_description :
+ R.string.mediapicker_gallery_image_item_description_no_date;
+ String contentDescription = String.format(getResources().getString(templateId),
+ dateSeconds * TimeUnit.SECONDS.toMillis(1));
+ mImageView.setContentDescription(contentDescription);
} else {
mImageView.setScaleType(ScaleType.CENTER_CROP);
setBackgroundColor(getResources().getColor(R.color.gallery_image_default_background));
@@ -156,4 +238,17 @@ public class GalleryGridItemView extends FrameLayout {
mImageView.setContentDescription(contentDescription);
}
}
+
+ private void showVideoPlayButtonOverlay() {
+ if (mVideoButtonOverlayView != null) {
+ mVideoButtonOverlayView.setVisibility(View.VISIBLE);
+ }
+ }
+
+ private void hideVideoPlayButtonOverlay() {
+ if (mVideoButtonOverlayView != null) {
+ mVideoButtonOverlayView.setVisibility(View.INVISIBLE);
+ }
+ }
+
}
diff --git a/src/com/android/messaging/ui/mediapicker/GalleryGridView.java b/src/com/android/messaging/ui/mediapicker/GalleryGridView.java
index a5a7dad..62f7f6f 100644
--- a/src/com/android/messaging/ui/mediapicker/GalleryGridView.java
+++ b/src/com/android/messaging/ui/mediapicker/GalleryGridView.java
@@ -34,6 +34,7 @@ import com.android.messaging.datamodel.data.DraftMessageData;
import com.android.messaging.datamodel.data.GalleryGridItemData;
import com.android.messaging.datamodel.data.MessagePartData;
import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageDataListener;
+import com.android.messaging.datamodel.data.ParticipantData;
import com.android.messaging.ui.PersistentInstanceState;
import com.android.messaging.util.Assert;
import com.android.messaging.util.ContentType;
@@ -69,11 +70,20 @@ public class GalleryGridView extends MediaPickerGridView implements
private boolean mIsMultiSelectMode = false;
private ImmutableBindingRef<DraftMessageData> mDraftMessageDataModel;
+ /** Provides subscription-related data to access per-subscription configurations. */
+ private DraftMessageData.DraftMessageSubscriptionDataProvider mSubscriptionDataProvider;
+
+
public GalleryGridView(final Context context, final AttributeSet attrs) {
super(context, attrs);
mSelectedImages = new ArrayMap<Uri, MessagePartData>();
}
+ public void setSubscriptionProvider(DraftMessageData.DraftMessageSubscriptionDataProvider
+ provider) {
+ mSubscriptionDataProvider = provider;
+ }
+
public void setHostInterface(final GalleryGridViewListener hostInterface) {
mListener = hostInterface;
}
@@ -121,6 +131,16 @@ public class GalleryGridView extends MediaPickerGridView implements
return mIsMultiSelectMode;
}
+ @Override
+ public int getSubscriptionProviderSubId() {
+ if (mSubscriptionDataProvider != null) {
+ return mSubscriptionDataProvider.getConversationSelfSubId();
+ } else {
+ return ParticipantData.DEFAULT_SELF_SUB_ID;
+ }
+ }
+
+
private void toggleItemSelection(final Rect startRect, final GalleryGridItemData data) {
Assert.isTrue(isMultiSelectEnabled());
if (isItemSelected(data)) {
diff --git a/src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java b/src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java
index 9422386..af360bc 100644
--- a/src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java
+++ b/src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java
@@ -21,7 +21,9 @@ import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.MergeCursor;
+import android.provider.Telephony;
import android.support.v7.app.ActionBar;
+import android.support.v7.mms.CarrierConfigValuesLoader;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -29,6 +31,7 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.Toast;
import com.android.messaging.Factory;
import com.android.messaging.R;
import com.android.messaging.datamodel.data.GalleryGridItemData;
@@ -37,6 +40,7 @@ import com.android.messaging.datamodel.data.MessagePartData;
import com.android.messaging.datamodel.data.MediaPickerData.MediaPickerDataListener;
import com.android.messaging.util.Assert;
import com.android.messaging.util.OsUtil;
+import com.android.messaging.util.UriUtil;
/**
* Chooser which allows the user to select one or more existing images or videos
@@ -46,6 +50,7 @@ class GalleryMediaChooser extends MediaChooser implements
private final GalleryGridAdapter mAdapter;
private GalleryGridView mGalleryGridView;
private View mMissingPermissionView;
+ private static final String TAG = GalleryMediaChooser.class.getSimpleName();
GalleryMediaChooser(final MediaPicker mediaPicker) {
super(mediaPicker);
@@ -127,6 +132,7 @@ class GalleryMediaChooser extends MediaChooser implements
mGalleryGridView = (GalleryGridView) view.findViewById(R.id.gallery_grid_view);
mAdapter.setHostInterface(mGalleryGridView);
+ mGalleryGridView.setSubscriptionProvider(this);
mGalleryGridView.setAdapter(mAdapter);
mGalleryGridView.setHostInterface(this);
mGalleryGridView.setDraftMessageDataModel(mMediaPicker.getDraftMessageDataModel());
@@ -172,6 +178,7 @@ class GalleryMediaChooser extends MediaChooser implements
if (data instanceof Cursor) {
rawCursor = (Cursor) data;
}
+
// Before delivering the cursor, wrap around the local gallery cursor
// with an extra item for document picker integration in the front.
final MatrixCursor specialItemsCursor =
@@ -188,6 +195,9 @@ class GalleryMediaChooser extends MediaChooser implements
// Work around a bug in MediaStore where cursors querying the Files provider don't get
// updated for changes to Images.Media or Video.Media.
startMediaPickerDataLoader();
+ updateForPermissionState(true);
+ } else {
+ updateForPermissionState(false);
}
}
diff --git a/src/com/android/messaging/ui/mediapicker/MediaPicker.java b/src/com/android/messaging/ui/mediapicker/MediaPicker.java
index f441d09..6692620 100644
--- a/src/com/android/messaging/ui/mediapicker/MediaPicker.java
+++ b/src/com/android/messaging/ui/mediapicker/MediaPicker.java
@@ -178,6 +178,7 @@ public class MediaPicker extends Fragment implements DraftMessageSubscriptionDat
mChoosers = new MediaChooser[] {
new CameraMediaChooser(this),
new GalleryMediaChooser(this),
+ new AudioListChooser(this),
new AudioMediaChooser(this),
};
@@ -725,6 +726,7 @@ public class MediaPicker extends Fragment implements DraftMessageSubscriptionDat
protected static final int LOCATION_PERMISSION_REQUEST_CODE = 2;
protected static final int RECORD_AUDIO_PERMISSION_REQUEST_CODE = 3;
protected static final int GALLERY_PERMISSION_REQUEST_CODE = 4;
+ protected static final int AUDIO_LIBRARY_PERMISSION_REQUEST_CODE = 5;
@Override
public void onRequestPermissionsResult(
diff --git a/src/com/android/messaging/ui/mediapicker/MediaPickerListView.java b/src/com/android/messaging/ui/mediapicker/MediaPickerListView.java
new file mode 100644
index 0000000..f448b87
--- /dev/null
+++ b/src/com/android/messaging/ui/mediapicker/MediaPickerListView.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.messaging.ui.mediapicker;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.GridView;
+import android.widget.ListView;
+
+public class MediaPickerListView extends ListView {
+
+ public MediaPickerListView(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ /**
+ * Returns if the list view can be swiped down further. It cannot be swiped down
+ * if there's no item or if we are already at the top.
+ */
+ public boolean canSwipeDown() {
+ if (getAdapter() == null || getAdapter().getCount() == 0 || getChildCount() == 0) {
+ return false;
+ }
+
+ final int firstVisiblePosition = getFirstVisiblePosition();
+ if (firstVisiblePosition == 0 && getChildAt(0).getTop() >= 0) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/com/android/messaging/util/NotificationUtil.java b/src/com/android/messaging/util/NotificationUtil.java
new file mode 100644
index 0000000..6afa961
--- /dev/null
+++ b/src/com/android/messaging/util/NotificationUtil.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.messaging.util;
+
+import android.content.Context;
+
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+
+public class NotificationUtil {
+ /**
+ * Get the final enabled status for notifications in this conversation, based on the given
+ * value, and the default (application-wide) value.
+ *
+ * @param conversationVal the custom value for this conversation, or -1 if it does not exist.
+ * @return whether notifications should be enabled for this conversation.
+ */
+ public static boolean getConversationNotificationEnabled(int conversationVal) {
+ return getEnabledCustomOrDefault(conversationVal, R.string.notifications_enabled_pref_key);
+ }
+
+ /**
+ * Get the final enabled status for notification vibration in this conversation, based on the
+ * given value and the default (application-wide) value.
+ *
+ * @param conversationVal the custom value for this conversation, or -1 if it does not exist.
+ * @return whether notification vibration should be enabled for this conversation.
+ */
+ public static boolean getConversationNotificationVibrateEnabled(int conversationVal) {
+ return getEnabledCustomOrDefault(conversationVal, R.string.notification_vibration_pref_key);
+ }
+
+ private static boolean getEnabledCustomOrDefault(int customVal, int keyRes) {
+ // Load default if we do not have a custom value set.
+ if (customVal == -1) {
+ final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
+ final Context context = Factory.get().getApplicationContext();
+ final String prefKey = context.getString(keyRes);
+ return prefs.getBoolean(prefKey, true);
+ } else {
+ return customVal == 1;
+ }
+ }
+}
diff --git a/src/com/android/messaging/util/UiUtils.java b/src/com/android/messaging/util/UiUtils.java
index 84fe353..a9a1dc4 100644
--- a/src/com/android/messaging/util/UiUtils.java
+++ b/src/com/android/messaging/util/UiUtils.java
@@ -132,6 +132,16 @@ public class UiUtils {
null /* placement */);
}
+ public static void showSnackBar(final Context context, @NonNull final View parentView,
+ final String message) {
+ Assert.notNull(context);
+ Assert.isTrue(!TextUtils.isEmpty(message));
+ SnackBarManager.get()
+ .newBuilder(parentView)
+ .setText(message)
+ .show();
+ }
+
public static void showSnackBarWithCustomAction(final Context context,
@NonNull final View parentView,
@NonNull final String message,
diff --git a/src/com/cyanogenmod/messaging/util/PrefsUtils.java b/src/com/cyanogenmod/messaging/util/PrefsUtils.java
new file mode 100644
index 0000000..7247bdb
--- /dev/null
+++ b/src/com/cyanogenmod/messaging/util/PrefsUtils.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.cyanogenmod.messaging.util;
+
+import android.content.Context;
+import com.android.messaging.Factory;
+import com.android.messaging.R;
+import com.android.messaging.util.BuglePrefs;
+
+public class PrefsUtils {
+ public static final String SHOW_EMOTICONS_ENABLED = "pref_show_emoticons";
+
+ private PrefsUtils() {
+ //Don't instantiate
+ }
+
+ /**
+ * Returns whether or not swipe to dismiss in the ConversationListFragment deletes
+ * the conversation rather than archiving it.
+ * @return hopefully true
+ */
+ public static boolean isSwipeRightToDeleteEnabled() {
+ final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
+ final Context context = Factory.get().getApplicationContext();
+ final String prefKey = context.getString(R.string.swipe_right_deletes_conversation_key);
+ final boolean defaultValue = context.getResources().getBoolean(
+ R.bool.swipe_right_deletes_conversation_default);
+ return prefs.getBoolean(prefKey, defaultValue);
+ }
+
+ public static boolean isShowEmoticonsEnabled() {
+ final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
+ final Context context = Factory.get().getApplicationContext();
+ final boolean defaultValue = context.getResources().getBoolean(
+ R.bool.show_emoticons_pref_default);
+ return prefs.getBoolean(SHOW_EMOTICONS_ENABLED, defaultValue);
+ }
+}
diff --git a/tests/src/com/android/messaging/datamodel/FakeDataModel.java b/tests/src/com/android/messaging/datamodel/FakeDataModel.java
index 5e80eab..c411fb2 100644
--- a/tests/src/com/android/messaging/datamodel/FakeDataModel.java
+++ b/tests/src/com/android/messaging/datamodel/FakeDataModel.java
@@ -23,6 +23,7 @@ import android.test.RenamingDelegatingContext;
import com.android.messaging.datamodel.action.ActionService;
import com.android.messaging.datamodel.action.BackgroundWorker;
+import com.android.messaging.datamodel.data.AudioListItemData;
import com.android.messaging.datamodel.data.BlockedParticipantsData;
import com.android.messaging.datamodel.data.BlockedParticipantsData.BlockedParticipantsDataListener;
import com.android.messaging.datamodel.data.ContactListItemData;
@@ -165,6 +166,11 @@ public class FakeDataModel extends DataModel {
}
@Override
+ public AudioListItemData createAudioListItemData() {
+ return new AudioListItemData();
+ }
+
+ @Override
public LaunchConversationData createLaunchConversationData(
final LaunchConversationDataListener listener) {
return new LaunchConversationData(listener);